sql >> データベース >  >> RDS >> Mysql

大量のデータを配信するクエリに最適なMySQL設定?

    同様のハードウェアで同じことを60秒未満で実行できるのに、クエリの実行に2時間かかるには、何かが深刻に間違っている必要があります。

    次のいくつかは役立つかもしれません...

    エンジンに合わせてMySQLを調整する

    サーバー構成を確認し、それに応じて最適化します。次のリソースのいくつかが役立つはずです。

    さて、あまり明白ではありません...

    ストアドプロシージャを使用してデータサーバー側を処理することを検討してください

    MySQL内ですべてのデータを処理して、アプリケーション層に大量のデータを送信する必要がないようにしましょう。次の例では、カーソルを使用して、サーバー側で2分以内に5,000万行をループして処理します。私はカーソルの大ファンではありません。特に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;
    

    例とほぼ同じカーディナリティを持つキーフィールドを使用して、1億行のデータを生成しました:

    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は1つのファイルをデータ行に使用し、別のファイルをインデックスレコードに使用します。

    詳細はこちら:

    簡略化された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の問題の1つは、複合キーの一部を形成するauto_incrementフィールドをサポートしていないため、シーケンスジェネレーター、トリガー、またはその他の方法を使用して、増分キー値を自分で提供する必要があることです。 ??

    ここでも、例とほぼ同じカーディナリティを持つキーフィールドを使用して1億行のデータを生成しました。 innodbはカーディナリティを推定するため、これらの数値がmyisamの例と一致しなくても心配しないでください。したがって、これらの数値はまったく同じにはなりません。 (ただし、使用されているのと同じデータセットです)

    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エンジンの実装よりも! (innodb FTW)

    分割統治法

    カーソルを使用するサーバー側のストアドプロシージャで結果を処理することは、特にMySQLがC#などの3GL言語やその他のデータベースですぐに利用できる配列や複雑なデータ構造などをサポートしていないため、最適なソリューションではない可能性があります。 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
    

    したがって、6000万行がフェッチされ、60秒以内にコレクションに追加されました。

    完了するのに2分かかったmyisamストアドプロシージャを使用して同じことを試しました。

    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で結果を並べ替えると、処理が高速化されます。

    このいくつかがお役に立てば幸いです。

    頑張ってください




    1. 2つ(またはそれ以上)の行名を持つクロス集計

    2. PostgreSQLでの数値フォーマット用のテンプレートパターンと修飾子

    3. プリペアドステートメントでLIKE'%{$ var}%'を使用する正しい方法は? [mysqli]

    4. MicrosoftSQLServerエラー18456のトラブルシューティング