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

推薦する

MySQLクエリは、フィールドが数値とカンマではないことを指定します。

コアSQL文数字を含まない MySQL クエリ ステートメント: SELECT * FROM tes...

InnoDB エンジンの redo ファイルのメンテナンス方法

InnoDB REDO ログ ファイルのサイズと数を調整する場合は、次の手順に従います。 1. My...

HTML4.0 要素のデフォルトスタイルの配置

コードをコピーコードは次のとおりです。 html、アドレス、引用ブロック、本文、dd、div、 dl...

Dockerコマンドは一般ユーザーが実行できるように実装されている

dockerをインストールすると、通常はdockerユーザーグループが作成されます。ステップ2: 現...

Docker コンテナで ASP.NET Core を実行する手順

最近は学ぶべき知識が多すぎて、どれを先に学べばいいのかわかりません。このブログはもともとxamari...

Linuxはlsof/extundeleteツールを使用して、誤って削除されたファイルやディレクトリを復元します。

序文Linux には Windows のような目立つごみ箱がないため、簡単に復元することはできません...

Ubuntu 18.04 で apt-get ソースを変更する方法

apt-get を使用してインストールすると、非常に遅くなります。国内のソースを変更すると、この問題...

JavaScript での正規表現の使用について詳しく学ぶ

目次1. 正規表現とは何か1. 正規表現の特徴2. 正規表現の使用2. 正規表現における特殊文字1....

Linuxで新しいユーザーを作成し、指定されたディレクトリへの権限を付与する

1 ユーザーを作成し、ユーザーのルートパスとパスワードを指定します useradd -d /home...

Vuexの役割についての深い理解

目次概要コンポーネント間でデータを共有する方法Vuex の原則の紹介Vuexはコンポーネントのグロー...

SpringBootはActiviti7実装コードを統合する

Activiti7 の正式リリース以降、SpringBoot2.x との統合開発を全面的にサポートし...

Linux カーネル デバイス ドライバーのメモリ管理に関する注意事項

/************************ * Linux メモリ管理 *********...

キャッシュサーバーを構築するためのMemcached方式

序文多くの Web アプリケーションは、リレーショナル データベース管理システム (RDBMS) に...

Ubuntu 18.0.4 MySQL 8.0.20 のインストールと設定方法のグラフィックチュートリアル

MySQL 8.0.20 のインストールおよび設定方法は、参考としてグラフィック チュートリアルにま...

外部ファイル(js/vbs/css)をインポートするときに文字化けを回避する方法

ページ内にはjs、cssなどの外部ファイルが導入されており、外部ファイルのエンコードが現在のページフ...