Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Optimale MySQL-Einstellungen für Abfragen, die große Datenmengen liefern?

Irgendetwas muss ernsthaft schief gelaufen sein, damit die Ausführung Ihrer Abfrage 2 Stunden dauert, wenn ich dasselbe auf ähnlicher Hardware in weniger als 60 Sekunden erledigen kann.

Einige der folgenden Punkte könnten sich als hilfreich erweisen...

Passen Sie MySQL für Ihre Engine an

Überprüfen Sie Ihre Serverkonfiguration und optimieren Sie entsprechend. Einige der folgenden Ressourcen sollten nützlich sein.

Nun zu den weniger offensichtlichen...

Erwägen Sie die Verwendung einer gespeicherten Prozedur zur Verarbeitung der Datenserverseite

Warum verarbeiten Sie nicht alle Daten innerhalb von MySQL, damit Sie keine großen Datenmengen an Ihre Anwendungsschicht senden müssen? Im folgenden Beispiel wird ein Cursor verwendet, um serverseitig 50 Millionen Zeilen in weniger als 2 Minuten in einer Schleife zu verarbeiten und zu verarbeiten. Ich bin kein großer Fan von Cursorn, insbesondere in MySQL, wo sie sehr begrenzt sind, aber ich vermute, Sie würden die Ergebnismenge in einer Schleife durchlaufen und eine Form der numerischen Analyse durchführen, sodass die Verwendung eines Cursors in diesem Fall gerechtfertigt ist.

Vereinfachte myisam-Ergebnistabelle - Schlüssel basierend auf Ihren.

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;

Ich habe 100 Millionen Datenzeilen generiert, wobei die Schlüsselfelder ungefähr dieselbe Kardinalität wie in Ihrem Beispiel haben:

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   

Gespeicherte Prozedur

Ich habe eine einfache gespeicherte Prozedur erstellt, die die erforderlichen Daten abruft und verarbeitet (verwendet dieselbe Where-Bedingung wie in Ihrem Beispiel)

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 ; 

Folgende Laufzeiten wurden beobachtet:

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)

Hmmm, Leistung etwas enttäuschend, also weiter zur nächsten Idee.

Erwägen Sie die Verwendung der Innodb-Engine (Schock-Horror)

Warum innodb?? weil es geclusterte Indizes hat! Sie werden feststellen, dass das Einfügen mit Innodb langsamer ist, aber hoffentlich schneller zu lesen ist, also ist es ein Kompromiss, der sich lohnen könnte.

Der Zugriff auf eine Zeile über den gruppierten Index ist schnell, da sich die Zeilendaten auf derselben Seite befinden, auf die die Indexsuche führt. Wenn eine Tabelle groß ist, spart die Clustered-Index-Architektur im Vergleich zu Speicherorganisationen, die Zeilendaten auf einer anderen Seite als dem Indexdatensatz speichern, häufig einen Festplatten-E/A-Vorgang. Beispielsweise verwendet MyISAM eine Datei für Datenzeilen und eine andere für Indexeinträge.

Mehr Infos hier:

Vereinfachte innodb-Ergebnistabelle

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;

Ein Problem mit innodb besteht darin, dass es keine auto_increment-Felder unterstützt, die Teil eines zusammengesetzten Schlüssels sind, sodass Sie den inkrementierenden Schlüsselwert selbst mithilfe eines Sequenzgenerators, Triggers oder einer anderen Methode bereitstellen müssten – möglicherweise in der Anwendung, die die Ergebnistabelle selbst füllt ??

Auch hier habe ich 100 Millionen Datenzeilen generiert, wobei die Schlüsselfelder ungefähr dieselbe Kardinalität wie in Ihrem Beispiel haben. Machen Sie sich keine Sorgen, wenn diese Zahlen nicht mit dem myisam-Beispiel übereinstimmen, da innodb die Kardinalitäten schätzt, sodass sie nicht genau gleich sind. (aber sie sind - derselbe Datensatz wird verwendet)

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   

Gespeicherte Prozedur

Die gespeicherte Prozedur ist genau die gleiche wie im obigen myisam-Beispiel, wählt aber stattdessen Daten aus der innodb-Tabelle aus.

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

Die Ergebnisse lauten wie folgt:

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 Minuten schneller als die Implementierung der myisam-Engine! (innodb-FTW)

Teile und herrsche

Die Verarbeitung der Ergebnisse in einer serverseitigen gespeicherten Prozedur, die einen Cursor verwendet, ist möglicherweise keine optimale Lösung, zumal MySQL keine Unterstützung für Dinge wie Arrays und komplexe Datenstrukturen bietet, die in 3GL-Sprachen wie C # usw. oder sogar in anderen Datenbanken wie z wie Oracle PL/SQL.

Die Idee hier ist also, Datenstapel an eine Anwendungsschicht (C# was auch immer) zurückzugeben, die dann die Ergebnisse zu einer sammlungsbasierten Datenstruktur hinzufügen und die Daten dann intern verarbeiten kann.

Gespeicherte Prozedur

Die gespeicherte Prozedur benötigt 3 Parameter rc, df_low und df_high, mit denen Sie einen Datenbereich wie folgt auswählen können:

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

Je höher der df-Bereich, desto mehr Daten werden Sie natürlich extrahieren.

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 ; 

Ich habe auch eine myisam-Version erstellt, die bis auf die verwendete Tabelle identisch ist.

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

Basierend auf dem obigen Cursor-Beispiel würde ich erwarten, dass die innodb-Version die myisam-Version übertrifft.

Ich habe ein Quick and Dirty entwickelt C#-Anwendung mit mehreren Threads, die die gespeicherte Prozedur aufruft und die Ergebnisse zu einer Sammlung für die Verarbeitung nach der Abfrage hinzufügt. Sie müssen keine Threads verwenden, derselbe Stapelabfrageansatz könnte ohne großen Leistungsverlust sequentiell durchgeführt werden.

Jeder Thread (QueryThread) wählt einen Bereich von df-Daten aus, durchläuft die Ergebnismenge und fügt jedes Ergebnis (Zeile) der Ergebnissammlung hinzu.

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

Laufzeit wie folgt beobachtet:

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

So wurden in weniger als 60 Sekunden 50 Millionen Zeilen abgerufen und einer Sammlung hinzugefügt.

Ich habe dasselbe mit der gespeicherten Prozedur myisam versucht, die 2 Minuten dauerte.

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

Wechsel zu innodb

In meinem vereinfachten System schneidet die myisam-Tabelle nicht allzu schlecht ab, sodass es sich möglicherweise nicht lohnt, zu innodb zu migrieren. Wenn Sie sich entschieden haben, Ihre Ergebnisdaten in eine Innodb-Tabelle zu kopieren, gehen Sie wie folgt vor:

start transaction;

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

commit;

Das Bestellen des Ergebnisses durch den innodb PK vor dem Einfügen und Einpacken des Ganzen in eine Transaktion wird die Dinge beschleunigen.

Ich hoffe, einiges davon erweist sich als hilfreich.

Viel Glück