Noget skal være alvorligt galt, for at din forespørgsel tager 2 timer at udføre, når jeg kan gøre det samme på under 60 sekunder på lignende hardware.
Nogle af følgende kan vise sig at være nyttige...
Indstil MySQL til din motor
Tjek din serverkonfiguration og optimer i overensstemmelse hermed. Nogle af følgende ressourcer burde være nyttige.
- http ://www.mysqlperformanceblog.com/2006/09/29/what-to-tune-in-mysql-server-after-installation/
- http://www.mysqlperformanceblog.com/
- http://www.highperfmysql.com/
- http://forge.mysql.com/wiki/ServerVariables
- http://dev.mysql. com/doc/refman/5.0/en/server-system-variables.html
- http:/ /www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/
- http://jpipes.com/presentations/perf_tuning_best_practices.pdf
- http://jpipes.com/presentations/index_coding_optimization.pdf
- http://www.jasny.net/?p=36
Nu til det mindre indlysende...
Overvej at bruge en lagret procedure til at behandle dataserversiden
Hvorfor ikke behandle alle data inde i MySQL, så du ikke behøver at sende enorme mængder data til dit applikationslag? Følgende eksempel bruger en markør til at sløjfe og behandle 50M rækker på serversiden på under 2 minutter. Jeg er ikke en stor fan af markører, især i MySQL, hvor de er meget begrænsede, men jeg gætter på, at du ville sløjfe resultaterne og lave en form for numerisk analyse, så brug af en markør er berettiget i dette tilfælde.
Forenklet myisam-resultattabel - nøgler baseret på din.
drop table if exists results_1mregr_c_ew_f;
create table results_1mregr_c_ew_f
(
id int unsigned not null auto_increment primary key,
rc tinyint unsigned not null,
df int unsigned not null default 0,
val double(10,4) not null default 0,
ts timestamp not null default now(),
key (rc, df)
)
engine=myisam;
Jeg genererede 100 millioner rækker af data, hvor nøglefelterne havde omtrent samme kardinalitet som i dit eksempel:
show indexes from results_1mregr_c_ew_f;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Index_type
===== ========== ======== ============ =========== ========= =========== ==========
results_1mregr_c_ew_f 0 PRIMARY 1 id A 100000000 BTREE
results_1mregr_c_ew_f 1 rc 1 rc A 2 BTREE
results_1mregr_c_ew_f 1 rc 2 df A 223 BTREE
Lagret procedure
Jeg oprettede en simpel lagret procedure, der henter de nødvendige data og behandler dem (bruger samme tilstand som dit eksempel)
drop procedure if exists process_results_1mregr_c_ew_f;
delimiter #
create procedure process_results_1mregr_c_ew_f
(
in p_rc tinyint unsigned,
in p_df int unsigned
)
begin
declare v_count int unsigned default 0;
declare v_done tinyint default 0;
declare v_id int unsigned;
declare v_result_cur cursor for select id from results_1mregr_c_ew_f where rc = p_rc and df > p_df;
declare continue handler for not found set v_done = 1;
open v_result_cur;
repeat
fetch v_result_cur into v_id;
set v_count = v_count + 1;
-- do work...
until v_done end repeat;
close v_result_cur;
select v_count as counter;
end #
delimiter ;
Følgende kørselstider blev observeret:
call process_results_1mregr_c_ew_f(0,60);
runtime 1 = 03:24.999 Query OK (3 mins 25 secs)
runtime 2 = 03:32.196 Query OK (3 mins 32 secs)
call process_results_1mregr_c_ew_f(1,60);
runtime 1 = 04:59.861 Query OK (4 mins 59 secs)
runtime 2 = 04:41.814 Query OK (4 mins 41 secs)
counter
========
23000002 (23 million rows processed in each case)
Hmmmm, ydeevnen er lidt skuffende, så til næste idé.
Overvej at bruge innodb-motoren (chokhorror)
Hvorfor innodb ?? fordi det har klyngede indekser! Du vil opleve at indsætte langsommere ved at bruge innodb, men forhåbentlig vil det være hurtigere at læse, så det er en afvejning, der kan være det værd.
Det er hurtigt at få adgang til en række gennem det klyngede indeks, fordi rækkedataene er på den samme side, hvor indekssøgningen fører. Hvis en tabel er stor, gemmer den klyngede indeksarkitektur ofte en disk I/O-operation sammenlignet med lagerorganisationer, der gemmer rækkedata ved hjælp af en anden side fra indeksposten. For eksempel bruger MyISAM én fil til datarækker og en anden til indeksposter.
Mere info her:
Forenklet indodb-resultattabel
drop table if exists results_innodb;
create table results_innodb
(
rc tinyint unsigned not null,
df int unsigned not null default 0,
id int unsigned not null, -- cant auto_inc this !!
val double(10,4) not null default 0,
ts timestamp not null default now(),
primary key (rc, df, id) -- note clustered (innodb only !) composite PK
)
engine=innodb;
Et problem med innodb er, at det ikke understøtter auto_increment-felter, der udgør en del af en sammensat nøgle, så du skal selv angive den inkrementerende nøgleværdi ved hjælp af en sekvensgenerator, trigger eller en anden metode - måske i applikationen, der udfylder selve resultattabellen ??
Igen genererede jeg 100 millioner rækker af data med nøglefelterne med omtrent samme kardinalitet som i dit eksempel. Bare rolig, hvis disse tal ikke stemmer overens med myisam-eksemplet, da innodb estimerer kardinaliteterne, så de vil ikke være nøjagtigt ens. (men de er - samme datasæt brugt)
show indexes from results_innodb;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Index_type
===== ========== ======== ============ =========== ========= =========== ==========
results_innodb 0 PRIMARY 1 rc A 18 BTREE
results_innodb 0 PRIMARY 2 df A 18 BTREE
results_innodb 0 PRIMARY 3 id A 100000294 BTREE
Lagret procedure
Den lagrede procedure er nøjagtig den samme som myisam-eksemplet ovenfor, men vælger i stedet data fra innodb-tabellen.
declare v_result_cur cursor for select id from results_innodb where rc = p_rc and df > p_df;
Resultaterne er som følger:
call process_results_innodb(0,60);
runtime 1 = 01:53.407 Query OK (1 mins 53 secs)
runtime 2 = 01:52.088 Query OK (1 mins 52 secs)
call process_results_innodb(1,60);
runtime 1 = 02:01.201 Query OK (2 mins 01 secs)
runtime 2 = 01:49.737 Query OK (1 mins 50 secs)
counter
========
23000002 (23 million rows processed in each case)
ca. 2-3 minutter hurtigere end myisam-motorimplementeringen! (innodb FTW)
Del og hersk
Behandling af resultaterne i en lagret procedure på serversiden, der bruger en markør, er muligvis ikke en optimal løsning, især da MySQL ikke understøtter ting som arrays og komplekse datastrukturer, der er let tilgængelige på 3GL-sprog såsom C# osv. eller endda i andre databaser som f.eks. som Oracle PL/SQL.
Så ideen her er at returnere batches af data til et applikationslag (hvad som helst C#), som derefter kan tilføje resultaterne til en samlingsbaseret datastruktur og derefter behandle dataene internt.
Lagret procedure
Den lagrede procedure tager 3 parametre rc, df_low og df_high, som giver dig mulighed for at vælge en række data som følger:
call list_results_innodb(0,1,1); -- df 1
call list_results_innodb(0,1,10); -- df between 1 and 10
call list_results_innodb(0,60,120); -- df between 60 and 120 etc...
selvfølgelig, jo højere df-intervallet er, jo flere data vil du udtrække.
drop procedure if exists list_results_innodb;
delimiter #
create procedure list_results_innodb
(
in p_rc tinyint unsigned,
in p_df_low int unsigned,
in p_df_high int unsigned
)
begin
select rc, df, id from results_innodb where rc = p_rc and df between p_df_low and p_df_high;
end #
delimiter ;
Jeg har også slået en myisam-version op, som også er identisk med undtagelse af bordet, der bruges.
call list_results_1mregr_c_ew_f(0,1,1);
call list_results_1mregr_c_ew_f(0,1,10);
call list_results_1mregr_c_ew_f(0,60,120);
Baseret på markøreksemplet ovenfor ville jeg forvente, at innodb-versionen ville bedre end myisam-versionen.
Jeg udviklede en hurtig og beskidt multi-threaded C#-applikation, der kalder den lagrede procedure og tilføjer resultaterne til en samling til post-forespørgselsbehandling. Du behøver ikke bruge tråde, den samme batch-forespørgselstilgang kunne udføres sekventielt uden meget tab af ydeevne.
Hver tråd (QueryThread) vælger en række df-data, sløjfer resultatsættet og føjer hvert resultat (række) til resultatsamlingen.
class Program
{
static void Main(string[] args)
{
const int MAX_THREADS = 12;
const int MAX_RC = 120;
List<AutoResetEvent> signals = new List<AutoResetEvent>();
ResultDictionary results = new ResultDictionary(); // thread safe collection
DateTime startTime = DateTime.Now;
int step = (int)Math.Ceiling((double)MAX_RC / MAX_THREADS) -1;
int start = 1, end = 0;
for (int i = 0; i < MAX_THREADS; i++){
end = (i == MAX_THREADS - 1) ? MAX_RC : end + step;
signals.Add(new AutoResetEvent(false));
QueryThread st = new QueryThread(i,signals[i],results,0,start,end);
start = end + 1;
}
WaitHandle.WaitAll(signals.ToArray());
TimeSpan runTime = DateTime.Now - startTime;
Console.WriteLine("{0} results fetched and looped in {1} secs\nPress any key", results.Count, runTime.ToString());
Console.ReadKey();
}
}
Runtime observeret som følger:
Thread 04 done - 31580517
Thread 06 done - 44313475
Thread 07 done - 45776055
Thread 03 done - 46292196
Thread 00 done - 47008566
Thread 10 done - 47910554
Thread 02 done - 48194632
Thread 09 done - 48201782
Thread 05 done - 48253744
Thread 08 done - 48332639
Thread 01 done - 48496235
Thread 11 done - 50000000
50000000 results fetched and looped in 00:00:55.5731786 secs
Press any key
Så 50 millioner rækker blev hentet og tilføjet til en samling på under 60 sekunder.
Jeg prøvede det samme ved at bruge myisam-lagrede procedure, som tog 2 minutter at fuldføre.
50000000 results fetched and looped in 00:01:59.2144880 secs
Flytter til innodb
I mit forenklede system fungerer myisam-tabellen ikke så dårligt, så det er måske ikke værd at migrere til innodb. Hvis du beslutter dig for at kopiere dine resultatdata til en innodb-tabel, så gør det som følger:
start transaction;
insert into results_innodb
select <fields...> from results_1mregr_c_ew_f order by <innodb primary key>;
commit;
At bestille resultatet af innodb PK før indsættelse og indpakning af det hele i en transaktion vil fremskynde tingene.
Jeg håber, at noget af dette viser sig at være nyttigt.
Held og lykke