Нещо трябва да е сериозно нередно, за да може заявката ви да се изпълни за 2 часа, когато мога да направя същото за по-малко от 60 секунди на подобен хардуер.
Някои от следните може да се окажат полезни...
Настройте MySQL за вашия двигател
Проверете конфигурацията на вашия сървър и оптимизирайте съответно. Някои от следните ресурси трябва да са полезни.
- 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
Сега за по-малко очевидното...
Помислете за използването на съхранена процедура за обработка от страна на сървъра за данни
Защо не обработвате всички данни в MySQL, така че да не се налага да изпращате огромни количества данни към слоя на приложението си? Следващият пример използва курсор за зацикляне и обработка на 50M реда от страна на сървъра за по-малко от 2 минути. Не съм голям фен на курсорите, особено в MySQL, където те са много ограничени, но предполагам, че ще превъртите набора от резултати и ще правите някаква форма на числен анализ, така че използването на курсор е оправдано в този случай.
Опростена таблица с резултати от myisam – ключове въз основа на вашите.
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;
Генерирах 100 милиона реда данни с ключовите полета с приблизително същата мощност като във вашия пример:
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
Запазена процедура
Създадох проста съхранена процедура, която извлича необходимите данни и ги обработва (използва същото условие where като вашия пример)
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 ;
Наблюдавани са следните времена на изпълнение:
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)
Хммм, представянето е малко разочароващо, така че към следващата идея.
Помислете за използването на двигателя innodb (шокиращ ужас)
Защо innodb ?? защото има клъстерирани индекси! Ще откриете, че вмъкването е по-бавно с innodb, но се надяваме, че ще бъде по-бързо за четене, така че това е компромис, който може да си струва.
Достъпът до ред през клъстерирания индекс е бърз, тъй като данните в реда са на същата страница, до която води търсенето в индекса. Ако таблицата е голяма, клъстерираната индексна архитектура често спестява дискова I/O операция в сравнение с организации за съхранение, които съхраняват данни от редове, използвайки различна страница от записа на индекса. Например MyISAM използва един файл за редове с данни и друг за индексни записи.
Повече информация тук:
Опростена таблица с резултати от 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;
Един проблем с innodb е, че не поддържа полета auto_increment, които са част от съставен ключ, така че ще трябва да предоставите стойността на нарастващия ключ сами, като използвате генератор на последователност, тригер или някакъв друг метод - може би в приложението, което попълва самата таблица с резултати ??
Отново генерирах 100 милиона реда данни, като ключовите полета имат приблизително същата мощност като във вашия пример. Не се притеснявайте, ако тези цифри не съвпадат с примера на myisam, тъй като innodb оценява кардиналите, така че те няма да бъдат точно еднакви. (но те са - използван е същият набор от данни)
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
Запазена процедура
Съхранената процедура е абсолютно същата като примера за myisam по-горе, но вместо това избира данни от таблицата innodb.
declare v_result_cur cursor for select id from results_innodb where rc = p_rc and df > p_df;
Резултатите са както следва:
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)
около 2-3 минути по-бързо отколкото реализацията на myisam engine! (innodb FTW)
Разделяй и владей
Обработката на резултатите в съхранена процедура от страна на сървъра, която използва курсор, може да не е оптимално решение, особено тъй като MySQL няма поддръжка за неща като масиви и сложни структури от данни, които са лесно достъпни на 3GL езици като C# и т.н. или дори в други бази данни, като като Oracle PL/SQL.
Така че идеята тук е да се връщат партиди данни на приложен слой (C# каквото и да е), който след това може да добави резултатите към структура от данни, базирана на колекция, и след това да обработва данните вътрешно.
Запазена процедура
Съхранената процедура приема 3 параметъра rc, df_low и df_high, което ви позволява да изберете диапазон от данни, както следва:
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...
очевидно колкото по-висок е диапазонът на df, толкова повече данни ще извличате.
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 ;
Също така извадих версия на myisam, която също е идентична с изключение на таблицата, която се използва.
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);
Въз основа на примера с курсора по-горе бих очаквал версията на innodb да превъзхожда тази на myisam.
Разработих бързо и мръсно многонишково C# приложение, което ще извика съхранената процедура и ще добави резултатите към колекция за обработка след заявка. Не е нужно да използвате нишки, същият подход на групирана заявка може да се извършва последователно без много загуба на производителност.
Всяка нишка (QueryThread) избира диапазон от df данни, обикаля набора от резултати и добавя всеки резултат (ред) към колекцията от резултати.
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();
}
}
Времето на изпълнение се наблюдава, както следва:
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
Така 50 милиона реда са извлечени и добавени към колекция за по-малко от 60 секунди.
Опитах същото с помощта на съхранената процедура myisam, която отне 2 минути.
50000000 results fetched and looped in 00:01:59.2144880 secs
Преминаване към innodb
В моята опростена система таблицата myisam не работи твърде зле, така че може да не си струва да мигрирате към innodb. Ако все пак сте решили да копирате резултатите си в таблица innodb, направете го по следния начин:
start transaction;
insert into results_innodb
select <fields...> from results_1mregr_c_ew_f order by <innodb primary key>;
commit;
Подреждането на резултата от innodb PK, преди да се вмъкне и обвие цялото нещо в транзакция, ще ускори нещата.
Надявам се част от това да се окаже полезно.
Успех