Něco musí být vážně špatně, aby spuštění vašeho dotazu trvalo 2 hodiny, když totéž mohu udělat za méně než 60 sekund na podobném hardwaru.
Některé z následujících mohou být užitečné...
Vylaďte MySQL pro svůj engine
Zkontrolujte konfiguraci serveru a podle toho optimalizujte. Některé z následujících zdrojů by měly být užitečné.
- 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
Nyní k tomu méně zřejmému...
Zvažte použití uložené procedury ke zpracování na straně datového serveru
Proč nezpracovat všechna data v MySQL, abyste nemuseli posílat obrovské množství dat do své aplikační vrstvy? Následující příklad používá kurzor ke smyčce a zpracování 50 milionů řádků na straně serveru za méně než 2 minuty. Nejsem velkým fanouškem kurzorů, zvláště v MySQL, kde jsou velmi omezené, ale hádám, že budete opakovat sadu výsledků a provádět nějakou formu numerické analýzy, takže použití kurzoru je v tomto případě ospravedlnitelné.
Zjednodušená tabulka výsledků myisam – klíče založené na vašich.
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;
Vygeneroval jsem 100 milionů řádků dat s klíčovými poli majícími přibližně stejnou mohutnost jako ve vašem příkladu:
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
Uložená procedura
Vytvořil jsem jednoduchou uloženou proceduru, která načte požadovaná data a zpracuje je (používá stejnou podmínku kde jako váš příklad)
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 ;
Byly pozorovány následující doby běhu:
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, výkon je trochu zklamáním, takže k dalšímu nápadu.
Zvažte použití enginu innodb (šoková hrůza)
Proč innodb?? protože má seskupené indexy! Zjistíte, že pomocí innodb bude vkládání pomalejší, ale doufejme, že bude rychlejší na čtení, takže je to kompromis, který by se mohl vyplatit.
Přístup k řádku prostřednictvím seskupeného indexu je rychlý, protože data řádku jsou na stejné stránce, kam vede vyhledávání indexu. Pokud je tabulka velká, architektura klastrovaného indexu často ušetří diskovou I/O operaci ve srovnání s organizacemi úložiště, které ukládají data řádků pomocí jiné stránky než záznam indexu. Například MyISAM používá jeden soubor pro datové řádky a druhý pro indexové záznamy.
Více informací zde:
Zjednodušená tabulka výsledků innodb
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;
Jeden problém s innodb je ten, že nepodporuje pole auto_increment, která tvoří součást složeného klíče, takže byste museli zadat hodnotu inkrementačního klíče sami pomocí generátoru sekvencí, spouštěče nebo nějaké jiné metody - možná v aplikaci vyplňující samotnou výsledkovou tabulku. ??
Opět jsem vygeneroval 100 milionů řádků dat s klíčovými poli majícími přibližně stejnou mohutnost jako ve vašem příkladu. Nedělejte si starosti, pokud tato čísla neodpovídají příkladu myisam, protože innodb odhaduje mohutnosti, takže nebudou úplně stejné. (ale jsou - použitá stejná datová sada)
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
Uložená procedura
Uložená procedura je přesně stejná jako výše uvedený příklad myisam, ale místo toho vybírá data z tabulky innodb.
declare v_result_cur cursor for select id from results_innodb where rc = p_rc and df > p_df;
Výsledky jsou následující:
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)
přibližně o 2–3 minuty rychlejší než implementace motoru myisam! (innodb FTW)
Rozděl a panuj
Zpracování výsledků v uložené proceduře na straně serveru, která používá kurzor, nemusí být optimálním řešením, zejména proto, že MySQL nepodporuje věci, jako jsou pole a složité datové struktury, které jsou snadno dostupné v jazycích 3GL, jako je C# atd., nebo dokonce v jiných databázích, jako je např. jako Oracle PL/SQL.
Myšlenka je tedy vracet dávky dat do aplikační vrstvy (cokoli v jazyce C#), která pak může přidat výsledky do datové struktury založené na kolekci a poté data interně zpracovat.
Uložená procedura
Uložená procedura má 3 parametry rc, df_low a df_high, což vám umožňuje vybrat rozsah dat následovně:
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...
čím vyšší je rozsah df, tím více dat budete extrahovat.
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 ;
Také jsem srazil verzi myisam, která je identická kromě tabulky, která je použita.
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);
Na základě výše uvedeného příkladu kurzoru bych očekával, že verze innodb překoná verzi myisam.
Vyvinul jsem rychlé a špinavé vícevláknová aplikace C#, která zavolá uloženou proceduru a přidá výsledky do kolekce pro zpracování po dotazu. Nemusíte používat vlákna, stejný přístup s dávkovým dotazem lze provést postupně bez velké ztráty výkonu.
Každé vlákno (QueryThread) vybere rozsah dat df, zacyklí sadu výsledků a přidá každý výsledek (řádek) do kolekce výsledků.
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();
}
}
Doba běhu pozorována následovně:
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
Takže 50 milionů řádků bylo načteno a přidáno do kolekce za méně než 60 sekund.
Zkoušel jsem totéž pomocí uložené procedury myisam, jejíž dokončení trvalo 2 minuty.
50000000 results fetched and looped in 00:01:59.2144880 secs
Přechod do innodb
V mém zjednodušeném systému si tabulka myisam nevede příliš špatně, takže nemusí mít cenu migrovat na innodb. Pokud se rozhodnete zkopírovat svá výsledná data do tabulky innodb, udělejte to následovně:
start transaction;
insert into results_innodb
select <fields...> from results_1mregr_c_ew_f order by <innodb primary key>;
commit;
Objednání výsledku pomocí innodb PK před vložením a zabalením celé věci do transakce vše urychlí.
Doufám, že něco z toho bude užitečné.
Hodně štěstí