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 コマンドの概要 (推奨)

推薦する

jsonファイルの書き方の詳細説明

目次JSONとはなぜこの技術なのでしょうか? JSONの使い方- データ形式- メモ- JSには2つ...

MySQL で大文字と小文字を区別しないように設定する方法

mysql は大文字と小文字を区別しないように設定されていますウィンドウズmysqlがインストールさ...

Vue3.0 でページング コンポーネントを手動でカプセル化する方法

この記事では、vue3.0の手動カプセル化ページングコンポーネントの具体的なコードを参考までに紹介し...

ウェブサイトデザインに関するヒント

実は、最近はウェブデザインについてよく耳にするようになりました。インターネット業界は今とても発展して...

ドロップダウンボックス選択コンポーネントを実装するためのネイティブ js

この記事の例では、ドロップダウンボックス選択コンポーネントを実装するためのjsの具体的なコードを参考...

Sqoop エクスポート マップ 100% 削減 0% さまざまな理由と解決策でスタック

私はこのようなバグを典型的な「ハムレット」バグと呼んでいます。これは、「エラーメッセージは同じだが、...

Docker を使って LEMP 環境を素早く構築する方法の例

LEMP(Linux + Nginx + MySQL + PHP)は、基本的に今日のWeb開発者にと...

jsを使用してカルーセル効果を実現する

今日は、参考までに、jsを使用してカルーセルマップの効果を実現する方法についてお話ししましょう。具体...

MySQL 5.7 に組み込まれているストレス テストの mysqlslap コマンドと構文の詳細な説明

序文mysqlslap は、MySQL サーバーへのクライアント負荷をシミュレートし、各ステージの時...

1 つの記事で React における Redux の初期の使用を理解する

Redux はデータ状態管理プラグインです。React や Vue を使用してコンポーネント化された...

Java+Tomcat 環境の展開とインストールのプロセス図

次に、Centos7 に Java+Tomcat をインストールします。インターネット上には多くの記...

HTML でのアンカーポイントの使用_PowerNode Java アカデミー

ここで、アンカー ポイントを制御するいくつかの状況をまとめてみましょう。 1. 同じページ <...

image/x-png の ContentType について

これにより、png ファイルのアップロードも不可能になりました (後で情報を調べたところ、レジストリ...

MySQLで一意のサーバーIDを生成する方法

序文MySQL では、server-id を使用してデータベース インスタンスを一意に識別し、それを...

Firefox または IE でスパン幅が決定されない場合の解決策

コードをコピーコードは次のとおりです。 <html xmlns="http://ww...