著者は、内部一時テーブルの使用をトリガーする MySQL のすべてのシナリオを要約しています。
著者: Liu Jiahao 氏。Axon チームの DBA のメンバーであり、真剣な競技ゲーム愛好家です。
Aikeson オープン ソース コミュニティによって作成されています。オリジナルのコンテンツを許可なく使用することはできません。転載する場合は編集者に連絡し、出典を示してください。
この記事は約 2,000 ワードの長さで、読むのに約 5 分かかります。
一時テーブルはデータを一時的に保存するテーブルです。このタイプのテーブルはセッションの終了時に自動的にクリーンアップされます。ただし、MySQL には 2 種類の一時テーブルがあり、1 つは外部一時テーブル、もう 1 つは外部一時テーブルです。内部一時テーブル。
外部一時テーブルとは、CREATE TEMPORARY TABLE
を使用してユーザーが手動で作成した一時テーブルを指します。内部一時テーブルはユーザー制御を超えており、外部一時テーブルのように CREATE ステートメントを使用して作成することはできません。MySQL のオプティマイザは、内部一時テーブルを使用するかどうかを自動的に選択します。
ここで疑問が生じます。MySQL はいつ内部一時テーブルを使用するのでしょうか?
UNION、GROUP BY、その他のシナリオを分析します。
ユニオンシーン
まずはテストシートを用意します。
CREATE TABLE `employees` (
`id` int NOT NULL AUTO_INCREMENT,
`first_name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
`last_name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
`sex` enum('M','F') COLLATE utf8mb4_bin DEFAULT NULL,
`age` int DEFAULT NULL,
`birth_date` date DEFAULT NULL,
`hire_date` date DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `last_name` (`last_name`),
KEY `hire_date` (`hire_date`)
) ENGINE=InnoDB AUTO_INCREMENT=500002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
データを挿入するスクリプトを準備します。
#! /usr/bin/python
#! coding=utf-8
import random
import pymysql
from faker import Faker
from datetime import datetime, timedelta
# 创建Faker实例
fake = Faker()
# MySQL连接参数
db_params = {
'host': 'localhost',
'user': 'root',
'password': 'root',
'db': 'db1',
'port': 3311
}
# 连接数据库
connection = pymysql.connect(**db_params)
# 创建一个新的Cursor实例
cursor = connection.cursor()
# 生成并插入数据
for i in range(5000):
id = (i+1)
first_name = fake.first_name()
last_name = fake.last_name()
sex = random.choice(['M', 'F'])
age = random.randint(20, 60)
birth_date = fake.date_between(start_date='-60y', end_date='-20y')
hire_date = fake.date_between(start_date='-30y', end_date='today')
query = f"""INSERT INTO employees (id, first_name, last_name, sex, age, birth_date, hire_date)
VALUES ('{id}', '{first_name}', '{last_name}', '{sex}', {age}, '{birth_date}', '{hire_date}');"""
cursor.execute(query)
# 每1000提交一次事务
if (i+1) % 1000 == 0:
connection.commit()
# 最后提交事务
connection.commit()
# 关闭连接
cursor.close()
connection.close()
テストデータ作成後、UNIONを使用した文を実行します。
root@localhost:mysqld.sock[db1]> explain (select 5000 as res from dual) union (select id from employees order by id desc limit 2);
+----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
| 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
| 2 | UNION | employees | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Backward index scan; Using index |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
3 rows in set, 1 warning (0.00 sec)
2 行目のキー値は PRIMARY であることがわかります。つまり、2 番目のクエリでは主キー ID が使用されています。 3 行目の追加の値は「Usingtemporary」で、上記の 2 つのクエリの結果セットを UNION するときに一時テーブルが使用されたことを示します。
UNION 演算は、重複を除いた 2 つの結果セットの結合です。これを行うには、まず主キーのみを持つメモリ内一時テーブルを作成し、最初のサブクエリの値をこのテーブルに挿入するだけで、重複の問題が回避されます。値 5000 は一時テーブルにすでに存在しており、2 番目のサブクエリの値 5000 は競合のため挿入できないため、次の値 4999 のみを挿入できます。
UNION ALL は、メモリ一時テーブルを使用しないという点で UNION とは異なります。次の例は、UNION ALL を使用した実行プランです。
root@localhost:mysqld.sock[db1]> explain (select 5000 as res from dual) union all (select id from employees order by id desc limit 2);
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
| 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
| 2 | UNION | employees | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Backward index scan; Using index |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+
2 rows in set, 1 warning (0.01 sec)
UNION ALL は重複排除を必要としないため、オプティマイザは重複排除を実行するために新しい一時テーブルを作成する必要がなく、実行中に 2 つのサブクエリを順番に実行し、結果セットにサブクエリを入れるだけで済みます。
UNION のセマンティクスの実装に関して、一時テーブルはデータを一時的に保存し、重複排除アクションを実行する役割を果たしていることがわかります。
グループ化
UNION に加えて、内部一時テーブルを使用する、より一般的に使用される句 GROUP BY もあります。次の例は、ID 列を使用して剰余を検索し、剰余のサイズに従って並べ替えてグループ統計を実行する方法を示しています。
root@localhost:mysqld.sock[db1]> explain select id%5 as complementation,count(*) from employees group by complementation order by 1;
+----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+----------------------------------------------+
| 1 | SIMPLE | employees | NULL | index | PRIMARY,last_name,hire_date | hire_date | 4 | NULL | 5000 | 100.00 | Using index; Using temporary; Using filesort |
+----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)
extra の値が、index を使用、一時テーブルを使用、およびファイルソートを使用していることがわかります。これら 3 つの値は、インデックスを使用、一時テーブルを使用、および並べ替えを使用しています。
注: MySQL バージョン 5.7 では、GROUP BY はデフォルトでフィールドをグループ化してソートしますが、MySQL バージョン 8.0 ではデフォルトのソート機能がキャンセルされるため、ここでは ORDER BY を使用して再現します。
GROUP BY の場合、上記のステートメントが実行された後、complementation
と < の値と主キーを保存するための内部一時テーブルがメモリ内に作成されます。 a i=2> の。次に、インデックス に対応する ID 値に従って、id%5 の値が として計算されます。一時テーブルに主キーがない場合は、が 値の場合、レコードは一時テーブルに挿入されます。一時テーブルが存在する場合は、この行のカウントが累積されます。上記の操作を行った後、ORDER BY のルールに従って を並べ替えます。 count(*)
complementation
hire_date
x
x
count(*)
complementation
グループ化に GROUP BY を使用する場合、または重複排除に DISTINCT を使用する場合、MySQL は、メモリ内一時テーブルの使用を避けるためのヒントを使用する方法を提供します。
ヒント | 説明する |
---|---|
SQL_BIG_RESULT | SQL ステートメントがディスク上の内部一時テーブルを使用することを明示的に指定します。これは、大規模なデータ ボリュームの操作に適しており、InnoDB エンジンとメモリ エンジンに適しています。 |
SQL_SMALL_RESULT | SQL ステートメントがメモリ内の内部一時テーブルを使用することを明示的に指定します。これは高速であり、データ量が少ない操作に適しており、メモリ エンジンに適しています。 |
以下は SQL_BIG_RESULT を使用した例です。
root@localhost:mysqld.sock[db1]> explain select SQL_BIG_RESULT id%5 as complementation,count(*) from employees group by complementation order by 1;
+----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | employees | NULL | index | PRIMARY,last_name,hire_date | hire_date | 4 | NULL | 5000 | 100.00 | Using index; Using filesort |
+----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
実行プランから、クエリに SQL_BIG_RESULT ヒントを使用した後、追加列の「Using Temporary」という単語が消えていることがわかります。これは、メモリ内の内部一時テーブルの使用が回避されたことを意味します。
その他のシーン
もちろん、上記の 2 つの例に加えて、MySQL は次の状況でも内部一時テーブルを作成します。
- UNION ステートメントの評価用。ただし、後で説明するいくつかの例外があります。
- TEMPTABLE アルゴリズム、UNION、または集計を使用するビューなど、特定のビューの評価用。
- 派生テーブルの評価。
- 公的表現の評価。
- サブクエリまたはセミ結合マテリアライゼーションに使用されるテーブル。
- ORDER BY 句と別の GROUP BY 句を含むステートメント、または ORDER BY 句または GROUP BY 句に結合キューの最初のテーブル以外のテーブルの列が含まれるステートメントの評価。
- DISTINCT と ORDER BY の組み合わせでは、一時テーブルが必要になる場合があります。
- SQL_SMALL_RESULT 修飾子を使用するクエリの場合、クエリにディスクに保存する必要がある要素も含まれていない限り、MySQL はメモリ内の一時テーブルを使用します。
- 同じテーブルを選択して挿入する INSERT ... SELECT ステートメントを評価するために、MySQL は SELECT 行を保持する内部一時テーブルを作成し、それらの行をターゲット テーブルに挿入します。
- 複数テーブルの UPDATE ステートメントの評価。
- GROUP_CONCAT() または COUNT(DISTINCT) 式の評価用。
- 必要に応じて一時テーブルを使用したウィンドウ関数の評価。
MySQL では、特定のクエリ条件に対してメモリ内の一時テーブルの使用が許可されていないことに注意してください。その場合、サーバーはディスク上の内部一時テーブルを使用します。
- テーブルに BLOB 列または TEXT 列が存在します。 MySQL 8.0 のメモリ内一時テーブルのデフォルトのストレージ エンジンである TempTable は、8.0.13 以降、バイナリ ラージ オブジェクト タイプをサポートします。
- UNION または UNION ALL を使用する場合、SELECT リストには最大長が 512 (バイナリ文字列の場合はバイト、非バイナリ文字列の場合は文字) を超える文字列列が存在します。
- SHOW COLUMNS および DESCRIBE ステートメントは一部の列の型として BLOB を使用するため、この結果に使用される一時テーブルはディスク上の一時テーブルになります。
参考文献
[1]: Ding Qi「MySQL45 講義」 37. 内部一時テーブルはいつ使用されますか?
[2]: 8.4.4 MySQL での内部一時テーブルの使用 URL:https://dev.mysql.com/doc/refman/8.0/en/internal-temporary -tables.html
さらに技術的な記事については、https://opensource.actionsky.com/ をご覧ください。
SQLEについて
SQLE は、開発環境から運用環境までの SQL 監査と管理をカバーする包括的な SQL 品質管理プラットフォームです。主流のオープンソース、商用および国内データベースをサポートし、開発、運用および保守のためのプロセス自動化機能を提供し、オンライン効率を向上させ、データ品質を向上させます。
SQL取得
タイプ | 住所 |
---|---|
リポジトリ | https://github.com/actiontech/sqle |
書類 | https://actiontech.github.io/sqle-docs/ |
リリースニュース | https://github.com/actiontech/sqle/releases |
データ監査プラグイン開発ドキュメント | https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse |