sql >> Database >  >> RDS >> Mysql

Optimale MySQL-instellingen voor queries die grote hoeveelheden data opleveren?

Er moet iets serieus mis zijn, anders duurt het 2 uur voordat je vraag is uitgevoerd, terwijl ik hetzelfde kan doen in minder dan 60 seconden op vergelijkbare hardware.

Sommige van de volgende dingen kunnen nuttig zijn...

Tune MySQL voor uw engine

Controleer uw serverconfiguratie en optimaliseer dienovereenkomstig. Sommige van de volgende bronnen zouden nuttig kunnen zijn.

Nu voor de minder voor de hand liggende...

Overweeg het gebruik van een opgeslagen procedure om de gegevensserverzijde te verwerken

Waarom verwerkt u niet alle gegevens in MySQL, zodat u geen grote hoeveelheden gegevens naar uw applicatielaag hoeft te sturen? In het volgende voorbeeld wordt een cursor gebruikt om 50 miljoen rijen aan serverzijde in minder dan 2 minuten te herhalen en te verwerken. Ik ben geen grote fan van cursors, vooral niet in MySQL waar ze erg beperkt zijn, maar ik vermoed dat je de resultatenset in een lus zou plaatsen en een of andere vorm van numerieke analyse zou doen, dus het gebruik van een cursor is in dit geval gerechtvaardigd.

Vereenvoudigde tabel met myisam-resultaten - sleutels gebaseerd op die van u.

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;

Ik heb 100 miljoen rijen met gegevens gegenereerd waarbij de sleutelvelden ongeveer dezelfde kardinaliteit hebben als in uw voorbeeld:

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   

Opgeslagen procedure

Ik heb een eenvoudige opgeslagen procedure gemaakt die de vereiste gegevens ophaalt en verwerkt (gebruikt dezelfde waar-voorwaarde als uw voorbeeld)

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 ; 

De volgende looptijden werden waargenomen:

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, prestatie viel een beetje tegen, dus op naar het volgende idee.

Overweeg het gebruik van de innodb-engine (shock horror)

Waarom innodb?? omdat het geclusterde indexen heeft! Je zult merken dat het invoegen langzamer gaat met innodb, maar hopelijk is het sneller te lezen, dus het is een afweging die het waard kan zijn.

Toegang tot een rij via de geclusterde index is snel omdat de rijgegevens zich op dezelfde pagina bevinden waar de indexzoekopdracht naartoe leidt. Als een tabel groot is, bespaart de geclusterde indexarchitectuur vaak een schijf-I/O-bewerking in vergelijking met opslagorganisaties die rijgegevens opslaan met een andere pagina dan het indexrecord. MyISAM gebruikt bijvoorbeeld het ene bestand voor gegevensrijen en het andere voor indexrecords.

Meer info hier:

Vereenvoudigde innodb-resultatentabel

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;

Een probleem met innodb is dat het geen auto_increment-velden ondersteunt die deel uitmaken van een samengestelde sleutel, dus u moet de waarde van de oplopende sleutel zelf opgeven met behulp van een reeksgenerator, trigger of een andere methode - misschien in de toepassing die de resultatentabel zelf vult ??

Nogmaals, ik heb 100 miljoen rijen gegevens gegenereerd waarbij de sleutelvelden ongeveer dezelfde kardinaliteit hebben als in uw voorbeeld. Maak je geen zorgen als deze cijfers niet overeenkomen met het myisam-voorbeeld, aangezien innodb de kardinaliteiten schat, zodat ze niet precies hetzelfde zullen zijn. (maar ze zijn - dezelfde gebruikte dataset)

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   

Opgeslagen procedure

De opgeslagen procedure is precies hetzelfde als het myisam-voorbeeld hierboven, maar selecteert in plaats daarvan gegevens uit de innodb-tabel.

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

De resultaten zijn als volgt:

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)

ongeveer 2-3 minuten sneller dan de myisam engine implementatie! (innodb FTW)

Verdeel en heers

Het verwerken van de resultaten in een aan de serverzijde opgeslagen procedure die een cursor gebruikt, is misschien geen optimale oplossing, vooral omdat MySQL geen ondersteuning biedt voor dingen zoals arrays en complexe gegevensstructuren die direct beschikbaar zijn in 3GL-talen zoals C # enz. Of zelfs in andere databases zoals als Oracle PL/SQL.

Het idee hier is dus om batches met gegevens terug te sturen naar een applicatielaag (C# wat dan ook) die de resultaten vervolgens kan toevoegen aan een op verzamelingen gebaseerde gegevensstructuur en de gegevens vervolgens intern kan verwerken.

Opgeslagen procedure

De opgeslagen procedure heeft 3 parameters rc, df_low en df_high, waarmee u als volgt een reeks gegevens kunt selecteren:

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

uiteraard geldt hoe hoger het df-bereik, hoe meer gegevens u extraheert.

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 ; 

Ik heb ook een myisam-versie gemaakt die identiek is, behalve de tafel die wordt gebruikt.

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

Op basis van het cursorvoorbeeld hierboven zou ik verwachten dat de innodb-versie beter presteert dan de myisam-versie.

Ik heb een quick and dirty . ontwikkeld multi-threaded C#-toepassing die de opgeslagen procedure aanroept en de resultaten toevoegt aan een verzameling voor verwerking na de query. U hoeft geen threads te gebruiken, dezelfde batch-querybenadering kan opeenvolgend worden uitgevoerd zonder veel prestatieverlies.

Elke thread (QueryThread) selecteert een reeks df-gegevens, geeft de resultatenset een lus en voegt elk resultaat (rij) toe aan de resultatenverzameling.

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 als volgt waargenomen:

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

Dus 50 miljoen rijen opgehaald en toegevoegd aan een verzameling in minder dan 60 seconden.

Ik heb hetzelfde geprobeerd met de myisam-opgeslagen procedure die 2 minuten duurde om te voltooien.

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

Verhuizen naar innodb

In mijn vereenvoudigde systeem presteert de myisam-tabel niet al te slecht, dus het is misschien niet de moeite waard om naar innodb te migreren. Als je hebt besloten om je resultaatgegevens naar een innodb-tabel te kopiëren, doe het dan als volgt:

start transaction;

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

commit;

Als u het resultaat bestelt door de innodb PK voordat u het geheel in een transactie plaatst en inpakt, gaat het sneller.

Ik hoop dat iets hiervan nuttig zal zijn.

Veel succes




  1. Aan de slag met SQL Server 2017 op Linux in de Azure-portal

  2. SQL-injectie-aanvallen voorkomen met Python

  3. 4 manieren om te controleren of een tabel bestaat voordat u deze in SQL Server (T-SQL) laat vallen

  4. SQL SERVER 2016 – Uitvoeringsplannen vergelijken