MySQLのGROUP BYステートメントを最適化する方法

MySQLのGROUP BYステートメントを最適化する方法

MySQL で、id、a、b の 3 つのフィールドを持つ新しいテーブルを作成します。次のように、同じフィールドを持つ 1,000 件のレコードを挿入します。

mysql> テーブル t1\G の作成を表示します
************************** 1. 行 ****************************
    表: t1
テーブルの作成: CREATE TABLE `t1` (
 `id` int(11) NULLではない、
 `a` int(11) デフォルト NULL,
 `b` int(11) デフォルト NULL,
 主キー (`id`)、
 キー `a` (`a`)
) エンジン=InnoDB デフォルト文字セット=utf8
セット内の 1 行 (0.00 秒)
mysql> t1 から * を選択して、制限 10 を設定します。
+----+------+------+
| id | a | b |
+----+------+------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 4 |
| 5 | 5 | 5 |
| 6 | 6 | 6 |
| 7 | 7 | 7 |
| 8 | 8 | 8 |
| 9 | 9 | 9 |
| 10 | 10 | 10 |
+----+------+------+
セット内の行数は 10 です (0.00 秒)

group by を含む次の SQL を実行すると、実行プランが表示されます。

mysql> explain select id%10 as m, count(*) as c from t1 group by m limit 10;
+----+-------------+--------+-----------+--------+-------+-------+--------+---------+----------------------------------------------+
| id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 |
+----+-------------+--------+-----------+--------+-------+-------+--------+---------+----------------------------------------------+
| 1 | SIMPLE | t1 | NULL | index | PRIMARY,a | a | 5 | NULL | 1000 | 100.00 | インデックスを使用; 一時を使用; ファイルソートを使用 |
+----+-------------+--------+-----------+--------+-------+-------+--------+---------+----------------------------------------------+
セットに 1 行、警告 1 件 (0.00 秒)

最後に次のものがあります:

  • インデックスの使用: カバーインデックス
  • 一時使用: 一時メモリテーブルの使用
  • ファイルソートの使用: ソート操作が使用される

この group by ステートメントの実行プロセスをよりよく理解するために、それを表す図を描きます。

上記の表と比較すると、この group by ステートメントの実行プロセスは次のようになることが簡単にわかります。

a. まず、2つのフィールドmとcを持つ一時メモリテーブルを作成します。主キーはmです。mはid%10、cはカウント数です(*)

b. テーブル t1 のインデックス a をスキャンし、リーフ ノードの id 値を 1 つずつ取り出し、id%10 の結果を計算して x として記録します。一時テーブルに主キー x を持つ行がない場合は、レコード (x,1) を挿入します。テーブルに主キー x を持つ行がある場合は、行 x の c 値に 1 を加えます。

c. トラバーサルが完了すると、フィールド m に従ってソートが実行され、結果セットがクライアントに返されます。 (この並べ替えアクションは、group by によって自動的に追加されることに注意してください。)

group by ステートメントで自動的に並べ替えを行わない場合は、ステートメントの最後に order by null を追加して、order by 後の並べ替えプロセスを削除できます。次のように:

mysql> explain select id%10 as m, count(*) as c from t1 group by m order by null;
+----+-------------+---------+-----------+--------+-------+-------+--------+---------+------------------------------+
| id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 |
+----+-------------+---------+-----------+--------+-------+-------+--------+---------+------------------------------+
| 1 | SIMPLE | t1 | NULL | index | PRIMARY,a | a | 5 | NULL | 1000 | 100.00 | インデックスを使用; 一時を使用 |
+----+-------------+---------+-----------+--------+-------+-------+--------+---------+------------------------------+
セットに 1 行、警告 1 件 (0.00 秒)

ご覧のとおり、explain の末尾の「using filesort」という単語が消えています。結果をもう一度見てみましょう:

mysql> t1 group by m から id%10 を m として選択し、 count(*) を c として選択します。
+------+-----+
| m | c |
+------+-----+
| 0 | 100 |
| 1 | 100 |
| 2 | 100 |
| 3 | 100 |
| 4 | 100 |
| 5 | 100 |
| 6 | 100 |
| 7 | 100 |
| 8 | 100 |
| 9 | 100 |
+------+-----+
セット内の行数は 10 です (0.00 秒)
mysql> select id%10 as m, count(*) as c from t1 group by m order by null;
+------+-----+
| m | c |
+------+-----+
| 1 | 100 |
| 2 | 100 |
| 3 | 100 |
| 4 | 100 |
| 5 | 100 |
| 6 | 100 |
| 7 | 100 |
| 8 | 100 |
| 9 | 100 |
| 0 | 100 |
+------+-----+
セット内の行数は 10 です (0.00 秒)

order by null を追加しない場合、group by によって自動的に並べ替えられるため、m=0 のレコードが最初の位置になります。order by null を追加すると、group by によって自動的に並べ替えられなくなり、m=0 のレコードが最後に配置されます。

現在のステートメントでは、テーブル t1 に合計 1000 件のレコードがあります。モジュロ 10 では、一時メモリ テーブルに配置できる結果は 10 件のみです。一時メモリ テーブルは、MySQL の tmp_table_size によって制御されます。

mysql> "%tmp_table%"のような変数を表示します。
+----------------+----------+
| 変数名 | 値 |
+----------------+----------+
| 最大tmpテーブル数 | 32 |
| tmp_table_size | 39845888 |
+----------------+----------+
セットに 2 行、警告 1 件 (0.00 秒)

結果が十分に大きく、メモリ内の一時テーブルが結果を保存するのに十分でない場合、MySQL はディスク上の一時テーブルを使用し、全体的なアクセス速度が非常に遅くなります。では、グループ化操作をどのように最適化すればよいのでしょうか?

01

最適化されたインデックスによるグループ化

上記の説明から、グループ化に group by を使用すると、作成される一時テーブルにはすべて一意のインデックスが付けられることがわかります。データ量が多い場合、group by の実行速度は非常に遅くなります。この状況を最適化するには、group by に一時テーブルが必要な理由を分析する必要があります。

この問題は、実際には、group by のロジックが異なる値の出現回数をカウントすることであるため発生します。group by 後のレコードの各行の結果は順序付けられていないため、これらの中間結果セットを格納するための一時テーブルが必要になります。私たちの価値観がすべて整然と整理されていたらどうなるでしょうか?

たとえば、レコード ID 列が次のテーブルがあるとします。

0,0,0,1,1,2,2,2,2,3,4,4,

group by を使用すると、同じ値を左から右に累積するだけです。この方法では一時テーブルは必要ありません。

上記の構造は誰もがよく知っています。データ列にインデックスを作成すると、列自体がソートされます。この列に基づいてグループ化すると、インデックスが自然にソートされるため、このプロセスでソートする必要はありません。この最適化を実現するには、次のようにテーブル t1 に新しい列 z を追加します。

mysql> alter table t1 add column z int generated always as(id % 10), add index(z);
クエリは正常、影響を受けた行は 0 行 (0.02 秒)
レコード: 0 重複: 0 警告: 0

mysql> t1 から z を m として選択し、 count(*) を c としてグループ化します。
+------+-----+
| m | c |
+------+-----+
| 0 | 100 |
| 1 | 100 |
| 2 | 100 |
| 3 | 100 |
| 4 | 100 |
| 5 | 100 |
| 6 | 100 |
| 7 | 100 |
| 8 | 100 |
| 9 | 100 |
+------+-----+
セット内の行数は 10 です (0.00 秒)

mysql> explain select z as m, count(*) as c from t1 group by z;
+----+-------------+--------+-----------+---------+---------------+-------+--------+----------+-----------+-------------+
| id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 |
+----+-------------+--------+-----------+---------+---------------+-------+--------+----------+-----------+-------------+
| 1 | SIMPLE | t1 | NULL | インデックス | z | z | 5 | NULL | 1000 | 100.00 | インデックスを使用 |
+----+-------------+--------+-----------+---------+---------------+-------+--------+----------+-----------+-------------+
セットに 1 行、警告 1 件 (0.00 秒)

id%10 の後の値を値とする新しいフィールド z を追加し、インデックスを作成します。次に、group by を使用して z 列をグループ化します。結果には一時テーブルがないことがわかります。

したがって、インデックスを使用すると、group by が依存する一時テーブルを削除するのに役立ちます。

02

グループ化の最適化---直接ソート

テーブル内のデータ量が非常に大きく、一時メモリ テーブルがソートを収容するのに明らかに不十分であることが既にわかっている場合は、group by にディスク ソートを実行するように指示することで、一時メモリ テーブルのソート プロセスを実際にスキップできます。

実際、MySQL には次のような方法があります。group by ステートメントに SQL_BIG_RESULT ヒントを追加すると、オプティマイザーに「このステートメントには大量のデータが含まれるので、ディスク一時テーブルを直接使用してください」と伝えることができます。このステートメントを使用すると、MySQL は、よく知られている B+ ツリーではなく、配列メソッドを自動的に使用して、ディスク一時テーブル内のフィールドを整理します。この知識ポイントに関して、公式文書では以下のように紹介されています。

SQL_BIG_RESULT または SQL_SMALL_RESULT を GROUP BY または DISTINCT とともに使用して、結果セットに多数の行があること、または結果セットが小さいことをそれぞれオプティマイザに通知できます。SQL_BIG_RESULT の場合、MySQL はディスクベースの一時テーブルが作成されている場合はそれを直接使用し、GROUP BY 要素にキーがある一時テーブルを使用するよりもソートを優先します。SQL_SMALL_RESULT の場合、MySQL はソートを使用する代わりにメモリ内の一時テーブルを使用して結果テーブルを格納します。これは通常は必要ありません。

グループ全体の処理プロセスは次のようになります。

a. sort_buffer を初期化し、そこに整数フィールド (m で示される) を配置します。

b. テーブルt1のインデックスaをスキャンし、その中のid値を1つずつ取り出し、id%100の値をsort_bufferに格納します。

c. スキャンが完了したら、sort_buffer のフィールド m をソートします (sort_buffer のメモリが不足している場合は、ディスク上の一時ファイルを使用してソートが行われます)。

d. ソートが完了すると、順序付けられた配列が得られます。 0、0、0、1、1、2、2、3、3、3、4、4、4、4のように

e. 順序付けられた配列に基づいて、配列内の異なる値と各値の出現回数を取得します。

昨日の記事では、union ステートメントでの一時テーブルの使用を分析しました。今日は、group by ステートメントでの一時テーブルの使用を分析しました。では、MySQL はいつ一時テーブルを使用するのでしょうか?

MySQL はいつ内部一時テーブルを使用しますか?

1. ステートメント実行プロセスがデータを読み取り、直接結果を取得できる場合、追加のメモリは必要ありません。そうでない場合は、中間結果を保存するために追加のメモリが必要です。

2. 実行ロジックで 2 次元テーブル機能を使用する必要がある場合は、一時テーブルが優先されます。たとえば、UNION では一意のインデックス制約が必要であり、GROUP BY では累積カウントを格納するための別のフィールドが必要です。

上記は、MySQL の group by ステートメントを最適化する方法の詳細です。MySQL の group by 最適化の詳細については、123WORDPRESS.COM の他の関連記事をご覧ください。

以下もご興味があるかもしれません:
  • MySQL データベース グループ クエリの group by ステートメントの詳細な説明
  • MySQL の group by と having の詳細な説明
  • MySQL の効率的なクエリの左結合とグループ化 (プラス インデックス)
  • MySQL Group by最適化の詳細な説明
  • MySQL の group by と order by を一緒に使用する方法
  • 複数のフィールドをグループ化するMySQLグループ
  • 各グループの最新データを取得するためにMySQLベースのグループを実装する
  • MySQL の group by に関する簡単な説明

<<:  Vueプロジェクトでvuexを使用する方法

>>:  一般的な docker コマンドの概要 (推奨)

推薦する

gorm で MySql データベースを操作する方法

1. テーブル内のフィールドの大文字と小文字の区別を設定するgorm クエリを使用する場合、MySQ...

CentOS8でyumソースを変更した後にウェアハウスキャッシュの同期に失敗する問題の詳細な説明

問題の原因: 最初は CentOS 8 のデフォルトの yum ソースを正常に使用できますが、次のコ...

MYSQL での Truncate の使用法の詳細な説明

この記事のガイド: テーブル内のデータを削除するには、削除と切り捨ての 2 つの方法があります。TR...

Linux でのソース パッケージ インストールのサービス管理

目次1. ソースパッケージサービスの起動管理2. ソースパッケージサービスのセルフスタート管理3. ...

Linux インストール MongoDB の起動と一般的な問題の解決

MongoDB のインストール プロセスと問題記録1. MongoDBのインストールMongoDBを...

Vue シングルページ アプリケーションで Markdown レンダリングを実装する

以前、Markdown をレンダリングするときに、mavonEditor のプレビュー モードを使用...

Docker環境を構築する簡単な方法

まず、Docker とは何かを理解しましょう。 Docker は、アプリケーションをデプロイするため...

Vueでaxiosをカプセル化する方法

目次1. インストール1. はじめに3. インターフェースルートアドレス4. 使用例4.1 ダウンロ...

Windows システム mysql5.7.18 インストール グラフィック チュートリアル

Windows システム向け MySQL インストール チュートリアルダウンロード1. https:...

Ubuntu で nvidia グラフィック ドライバーをインストールする (簡単なインストール方法)

Ubuntu で nvidia グラフィック カード ドライバーをインストールします。同じ方法で ...

フォームで完全な選択または逆選択効果を実現する JavaScript

この記事では、フォームの完全選択または逆選択を実現するためのJavaScriptの具体的なコードを参...

MySQL テーブルにはどのくらいの量のデータを保存できますか?

プログラマーは MySQL を扱う機会が多く、毎日触れているとも言えますが、MySQL テーブルには...

Javascript を使用して、スライドバー効果のあるスライドナビゲーション プラグインを開発します。

目次1. はじめに2. 使用方法3. 開発プロセス1. モデル例2. イベントとアニメーション4. ...

Nginx を使用してクロスドメイン Vue 開発環境を処理する方法

1. 需要正しい Cookie 配信と SSO テストを確実に実行できるように、ローカル テスト ド...