概要
長年の開発を経て、MySQL は最も人気のあるデータベースとなり、インターネット業界で広く使用され、徐々にさまざまな伝統的な業界にも浸透してきました。その人気の理由は、一方ではその優れた高同時トランザクション処理機能にあり、他方では MySQL の豊富なエコシステムの恩恵も受けていることです。 MySQL は、OLTP シナリオでの短いクエリの処理にはうまく機能しますが、複雑で大規模なクエリを処理する能力には限界があります。最も直接的な点は、MySQL が SQL ステートメントの処理に使用できるのは 1 つの CPU コアのみであり、このシナリオではホスト CPU のマルチコア機能を使用できないということです。 MySQL は立ち止まらず開発を続けており、新たにリリースされたバージョン 8.0.14 では、並列クエリ機能が初めて導入され、check table および select count(*) タイプのステートメントのパフォーマンスが 2 倍になりました。現状の利用シーンは比較的限られていますが、今後の展開に期待です。
推奨事項: "mysql ビデオ チュートリアル "
使用方法
パラメータ innodb_Parallel_read_threads を設定して同時スレッド数を設定します。並列スキャン機能を開始できます。デフォルト値は 4 です。ここでは簡単な実験を行います。sysbench を通じて 2 億個のデータをインポートし、innodb_Parallel_read_threads
をそれぞれ 1、2、4、8、16、32、64 に設定して、並列実行の効果をテストします。テスト ステートメントは select count(*) from sbtest1;
水平軸は構成された同時スレッドの数、垂直軸はステートメントの実行時間。テスト結果から判断すると、全体的な並列パフォーマンスは依然として良好で、2 億レコードのスキャン時間が 1 スレッドの 18 秒から 32 スレッドの 1 秒に短縮されました。今後、いくら同時実行性が進んでも、データ量が限られているため、マルチスレッドによる管理消費量が同時実行性による性能向上を上回り、SQL実行時間を短縮し続けることはできません。
MySQL 並列実行
実際、下の図に示すように、現在の MySQL の並列実行はまだ非常に初期の段階にあります。単一 SQL フォームの以前の MySQL シリアル処理、中央は現在の MySQL バージョンによって提供される並列機能、InnoDB エンジンの並列スキャンの形式、右端は MySQL が将来開発する形式です。オプティマイザーは、システム負荷と SQL に基づいて並列プランを生成し、そのパーティション プランを並列化のためにエグゼキューターに送信します。並列実行には、並列スキャンだけでなく、並列集計、並列結合、並列グループ化、並列ソートも含まれます。現在のバージョンの MySQL の上位レベルのオプティマイザおよびエグゼキュータに対するサポートされる変更はありません。したがって、以下の説明は主に、パーティショニング、並列スキャン、先読み、実行プログラムと対話するアダプター クラスなど、InnoDB エンジンが並列スキャンを実装する方法に焦点を当てています。
パーティショニング
並列スキャンの中心的なステップの 1 つはパーティショニングです。これは、スキャンされたデータを複数の部分に分割し、スレッドは並列スキャンできます。 InnoDB エンジンはインデックス構成テーブルであり、データはディスク上に B ツリー形式で保存され、ノードの単位はページ (ブロック/ページ) であり、同時にホット ページがバッファにキャッシュされます。プールされ、LRU アルゴリズムによって削除されます。パーティショニングのロジックは、ルートノードのページから階層ごとにスキャンし、ある階層の分岐数が設定したスレッド数を超えたと判断した場合に分割を停止します。実装中、実際には合計 2 つのパーティションが実行されます。最初のパーティションは、ルート ノード ページの枝の数に応じて分割されます。各枝の左端の葉ノードのレコードが左下限であり、このレコードは隣接する上限として記録される ブランチの右上の境界。このように、B ツリーはいくつかのサブツリーに分割され、各サブツリーはスキャン パーティションになります。最初のパーティション以降、パーティションの数がマルチコアを十分に活用できないという問題が発生する可能性があります。たとえば、並列スキャン スレッドが 3 に設定されている場合、最初のパーティションの後に 4 つのパーティションが生成され、その後、最初の 3 つのパーティションは並行して完了し、4 番目のパーティションは最大でも 1 つのスレッドによってのみスキャンでき、最終的な影響は、マルチコア リソースを完全に利用できなくなることです。
セカンダリ パーティショニング
この問題を解決するために、バージョン 8.0.17 ではセカンダリ パーティショニングが導入されました。複数のパーティションを同時にスキャンできます。InnoDB エンジンによる同時スキャンの最小粒度はページ レベルです。二次パーティショニングを判断するための具体的なロジックは、1 回のパーティショニングの後、パーティションの数がスレッドの数よりも大きい場合、スレッドの数よりも大きい数のパーティションを 2 回目のパーティショニングを継続する必要がある、というものです。パーティションの数がスレッドの数より少なく、B ツリー レベルが非常に深い場合、すべてのパーティションにセカンダリ パーティション化が必要です。
該当するコードは次のとおりです:
split_point = 0; if (ranges.size() > max_threads()) { //最后一批分区进行二次分区 split_point = (ranges.size() / max_threads()) * max_threads(); } else if (m_depth < SPLIT_THRESHOLD) { /* If the tree is not very deep then don't split. For smaller tables it is more expensive to split because we end up traversing more blocks*/ split_point = max_threads(); } else { //如果B+tree的层次很深(层数大于或等于3,数据量很大),则所有分区都需要进行二次分区 }
プライマリ パーティションであってもセカンダリ パーティションであっても、パーティション境界のロジックは同じであり、それぞれの左端のリーフ ノードのレコードパーティションは左下の境界であり、このレコードを隣接する前のブランチの右上の境界として記録します。これにより、十分なパーティション、十分な粒度、および十分な並列処理が確保されます。次の図は、セカンダリ パーティショニングをスキャンする 3 つの同時スレッドの構成を示しています。
関連するコードは次のとおりです:
create_ranges(size_t depth, size_t level) 一次分区: parallel_check_table add_scan partition(scan_range, level=0) /* start at root-page */ create_ranges(scan_range, depth=0, level=0) create_contexts(range, index >= split_point) 二次分区: split() partition(scan_range, level=1) create_ranges(depth=0,level)
並列スキャン
パーティションの後、各パーティション スキャン タスクをロックのないキューに入れます。並列ワーカー スレッドはキューからタスクを取得し、スキャン タスクを実行します。取得したタスクが分割属性を持つ場合、この時点でワーカータスクは 2 回に分割されてキューに入れられます。このプロセスには主に 2 つのコア インターフェイスが含まれており、1 つはワーカー スレッド インターフェイス、もう 1 つはトラバーサル レコード インターフェイスです。前者はキューからタスクを取得して実行し、統計カウントを維持します。後者は可視性に基づいて適切なレコードを取得し、インジェクトします。カウントなどの上位層のコールバック関数処理を通じてそれらを処理します。
Parallel_reader::worker(size_t thread_id)
{
1. ctx-queue から ctx タスクを抽出します
2. Split 属性に従ってctx の、パーティションをさらに分割する必要があるかどうかを判断します (split())
3. パーティション内のすべてのレコードを走査します (traverse())
4. パーティション タスクの完了後、m_n_completed カウントを維持します
5. m_n_compeleted カウントが ctx 数に達した場合、すべてのワーカー スレッドを起動し、終了します
6. トラバース インターフェイスに従ってエラー情報を返します。
}
Parallel_reader::Ctx::traverse()
{
1. rangeに従ってカーソルを設定します
2. btree を検索し、範囲の開始位置にカーソルを置きます。
3. 可視性を決定します (check_visibility)
4. 可視の場合は、コールバック関数 (統計など) に従って計算します
5. 逆方向に移動します。ページの最後のレコードに到達したら、先読みメカニズム (submit_read_ahead) を開始します。
6. 範囲を超えたら終了します。
}
同時に、8.0 バージョン .17 では、IO ボトルネックによる並列パフォーマンスの低下の問題を回避するための先読みメカニズムも導入されています。現在、事前読み取りのスレッド数は構成できず、コード内で 2 スレッドにハードコードされています。各事前読み取りの単位はクラスターです (InnoDB ファイルはセグメント、クラスター、ページの 3 レベルの構造で管理され、クラスターは連続したページのグループです)。ページ構成のサイズに応じて、 1M または 2M の場合があります。一般的な 16k ページ構成の場合、毎回 1M、つまり 64 ページが事前に読み取られます。ワーカー スレッドがスキャンするときは、まず次の隣接ページがクラスターの最初のページであるかどうかを判断し、そうであれば、事前読み取りタスクを開始します。先読みタスクもロックフリー キューを通じてキャッシュされ、ワーカー スレッドはプロデューサーであり、先読みワーカーはコンシューマです。すべてのパーティション ページは重複しないため、先読みタスクは繰り返されません。
Executor interaction (adapter)
実際、MySQL は、後続のより充実した並列実行に備えるために、上位層で使用するアダプター クラス Parallel_reader_adapter をカプセル化しています。まず第一に、このクラスはレコード形式の問題を解決し、エンジン層によってスキャンされたレコードを MySQL 形式に変換する必要があります。このようにして、上位層と下位層は分離されています。実行者はエンジン層の形式を感知する必要はありませんMySQL 形式で処理されます。プロセス全体は流れ作業です。MySQL レコードはバッファを介してバッチで保存されます。ワーカー スレッドはエンジン層からレコードを継続的に読み取ります。同時に、レコードは上位層によって継続的に処理されます。読み取りと処理の違いは次のとおりです。速度はバッファーによってバランスを取ることができ、プロセス全体が確実に流れるようにします。デフォルトのキャッシュ サイズは 2M です。バッファがキャッシュできる MySQL レコードの数は、テーブルのレコード行の長さに基づいて決定されます。コアプロセスは主に process_rows インターフェイスにあり、プロセスは次のとおりです。
process_rows
{
1. エンジン レコードを MySQL レコードに変換します
2このスレッドのバッファ情報を取得します (変換された mysql レコードの数と上位層に送信されたレコードの数)
3. MySQL レコードをバッファに入力し、統計を増分します m_n_read
4コールバック関数を呼び出して処理 (統計、集計、ソートなど)、自動インクリメント統計 m_n_send
}
呼び出し側では、メタ関数を設定する必要があります。テーブルの情報を取得し、集計、並べ替え、グループ作業などの処理レコードのコールバック関数を注入します。コールバック関数は、m_init_fn、m_load_fn、m_end_fn の設定によって制御されます。
概要
MySQL8.0 では並列クエリが導入されました。まだ比較的初歩的なものではありますが、MySQL 並列クエリの可能性をすでに認識できるようになりました。並列実行を有効にすると、SQL ステートメントの実行はマルチコア機能を最大限に活用し、応答時間が大幅に低下します。近い将来、8.0 では並列集計、並列接続、並列グループ化、並列ソートなど、より多くの並列演算子がサポートされるようになると思います。
以上がMySQL8.0 InnoDB並列実行の詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。