sql >> Databasteknik >  >> RDS >> Mysql

Optimala MySQL-inställningar för frågor som levererar stora mängder data?

Något måste vara allvarligt fel för att din fråga ska ta 2 timmar att köra när jag kan göra samma sak på mindre än 60 sekunder på liknande hårdvara.

En del av följande kan vara till hjälp...

Justera MySQL för din motor

Kontrollera din serverkonfiguration och optimera därefter. Några av följande resurser borde vara användbara.

Nu till det mindre uppenbara...

Överväg att använda en lagrad procedur för att behandla dataserversidan

Varför inte bearbeta all data inuti MySQL så att du inte behöver skicka stora mängder data till ditt applikationslager? Följande exempel använder en markör för att loopa och bearbeta 50 miljoner rader på serversidan på mindre än 2 minuter. Jag är inte ett stort fan av markörer, speciellt i MySQL där de är väldigt begränsade, men jag gissar att du skulle loopa resultatuppsättningen och göra någon form av numerisk analys så användning av en markör är motiverad i det här fallet.

Förenklad resultattabell för myisam – nycklar baserade 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;

Jag genererade 100 miljoner rader med data där nyckelfälten hade ungefär samma kardinalitet som i ditt exempel:

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   

Lagrad procedur

Jag skapade en enkel lagrad procedur som hämtar den nödvändiga informationen och bearbetar den (använder samma där villkor som ditt exempel)

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öljande körtider observerades:

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, prestanda lite nedslående så till nästa idé.

Överväg att använda innodb-motorn (chockskräck)

Varför innodb ?? eftersom det har klustrade index! Du kommer att tycka att det är långsammare att infoga med innodb men förhoppningsvis går det snabbare att läsa så det är en avvägning som kan vara värt det.

Det går snabbt att komma åt en rad genom det klustrade indexet eftersom raddata finns på samma sida dit indexsökningen leder. Om en tabell är stor, sparar den klustrade indexarkitekturen ofta en disk I/O-operation jämfört med lagringsorganisationer som lagrar raddata med en annan sida än indexposten. Till exempel använder MyISAM en fil för datarader och en annan för indexposter.

Mer info här:

Förenklad resultattabell för 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;

Ett problem med innodb är att det inte stöder auto_increment-fält som utgör en del av en sammansatt nyckel så du måste ange det ökande nyckelvärdet själv med en sekvensgenerator, trigger eller någon annan metod - kanske i applikationen som fyller i själva resultattabellen ??

Återigen genererade jag 100 miljoner rader med data där nyckelfälten hade ungefär samma kardinalitet som i ditt exempel. Oroa dig inte om dessa siffror inte stämmer överens med myisam-exemplet eftersom innodb uppskattar kardinaliteterna så att de inte blir exakt samma. (men de är - samma datauppsättning används)

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   

Lagrad procedur

Den lagrade proceduren är exakt densamma som myisam-exemplet ovan men väljer data från innodb-tabellen istället.

declare v_result_cur cursor for select id from results_innodb where rc = p_rc and df > p_df;

Resultaten är följande:

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 minuter snabbare än implementeringen av myisam-motorn! (innodb FTW)

Dela och erövra

Att bearbeta resultaten i en lagrad procedur på serversidan som använder en markör kanske inte är en optimal lösning, särskilt eftersom MySQL inte har stöd för saker som arrayer och komplexa datastrukturer som är lätt tillgängliga i 3GL-språk som C# etc eller till och med i andra databaser som t.ex. som Oracle PL/SQL.

Så tanken här är att returnera partier av data till ett applikationslager (C# oavsett) som sedan kan lägga till resultaten till en samlingsbaserad datastruktur och sedan bearbeta data internt.

Lagrad procedur

Den lagrade proceduren tar 3 paramatrar rc, df_low och df_high som låter dig välja ett dataintervall enligt följande:

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...

ju högre df-intervallet är, desto mer data kommer du att extrahera.

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 ; 

Jag knackade också upp en myisam-version som också är identisk förutom tabellen som används.

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);

Baserat på markörexemplet ovan skulle jag förvänta mig att innodb-versionen skulle överträffa myisam-versionen.

Jag utvecklade en snabb och smutsig flertrådig C#-applikation som anropar den lagrade proceduren och lägger till resultaten i en samling för bearbetning av efterfrågan. Du behöver inte använda trådar, samma satsvisa frågemetod kan göras sekventiellt utan större prestandaförlust.

Varje tråd (QueryThread) väljer ett intervall av df-data, loopar resultatuppsättningen och lägger till varje resultat (rad) till 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();
        }
    }

Körtid observerades enligt följande:

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 miljoner rader hämtas och läggs till en samling på under 60 sekunder.

Jag försökte samma sak med myisam lagrade proceduren som tog 2 minuter att slutföra.

50000000 results fetched and looped in 00:01:59.2144880 secs

Flytar till innodb

I mitt förenklade system fungerar myisam-tabellen inte så dåligt så det kanske inte är värt att migrera till innodb. Om du bestämmer dig för att kopiera dina resultatdata till en innodb-tabell gör du det så här:

start transaction;

insert into results_innodb 
 select <fields...> from results_1mregr_c_ew_f order by <innodb primary key>;

commit;

Att beställa resultatet av innodb PK innan du infogar och slår in det hela i en transaktion kommer att påskynda saken.

Jag hoppas att något av detta kan vara till hjälp.

Lycka till




  1. Hur man listar databaser och tabeller i PostgreSQL

  2. psycopg2:infoga flera rader med en fråga

  3. ALTER TABLE-satsen kom i konflikt med FOREIGN KEY-begränsningen i SQL Server - SQL Server / TSQL Tutorial Del 69

  4. Hur upprepar jag ett resurs-id #6 från ett MySql-svar i PHP?