[7,000 語] MySQL を使用してクエリ ステートメントを分析する方法を説明します

分析クエリ文: EXPLAIN


1。概要

遅いクエリ SQL を特定したら、EXPLAIN ツールまたは DESCRIBE ツールを使用して、対象を絞った分析とクエリを実行できます。どちらも使い方は同じで、解析結果も同じです。

MySQL には、特に SQL ステートメントの最適化を担当するオプティマイザー モジュールがあり、その主な機能は、システム内で収集された統計情報を計算および分析し、クライアントから要求されたクエリ (最適なデータ) に対して最適な実行計画を提供することです。検討した検索計画はあくまでも自動解析されるため、DBAや開発者が考える最適解とは限りません

この実行プランは、複数テーブル接続の順序各テーブルで特定のクエリを実行する方法など、次に特定のクエリを実行する方法を示します。MySQLが提供する EXPLAIN ステートメントを使用して、特定のクエリ ステートメントをクエリすることができます。 EXPLAIN 文の出力項目に応じてプランを実行することで、目標を絞った方法でクエリ SQL のパフォーマンスを向上させることができます。

何を見つけられますか?

  • テーブルの読み取り順序
  • データ読み取り操作の操作タイプ
  • どのインデックスが使用できるか
  • 実際に使用されるインデックス
  • テーブル間の参照関係
  • オプティマイザによってクエリされるテーブルあたりの行数

バージョンの違い

  • MySQL5.6.3以前はEXPLAIN SELECTのみ使用可能、その後はEXPLAIN SELECT、UPDATE、DELETEが使用可能
  • 5.7 より前のバージョンでは、パーティション (パーティション) とフィルターを表示するには EXPLAIN パーティションとフィルターを使用する必要がありましたが、5.7 以降はデフォルトで直接表示されます。

データの準備

テーブルを作成する

CREATE TABLE s1 (
	id INT AUTO_INCREMENT,
	key1 VARCHAR(100),
	key2 INT,
	key3 VARCHAR(100),
	key_part1 VARCHAR(100),
	key_part2 VARCHAR(100),
	key_part3 VARCHAR(100),
	common_field VARCHAR(100),
	PRIMARY KEY (id),
	INDEX idx_key1 (key1),
	UNIQUE INDEX idx_key2(key2),
	INDEX idx_key3(key3),
	INDEX idx_key_part(key_part1, key_part2, key_part3)
)ENGINE=INNODB CHARSET=utf8


CREATE TABLE s2 (
	id INT AUTO_INCREMENT,
	key1 VARCHAR(100),
	key2 INT,
	key3 VARCHAR(100),
	key_part1 VARCHAR(100),
	key_part2 VARCHAR(100),
	key_part3 VARCHAR(100),
	common_field VARCHAR(100),
	PRIMARY KEY (id),
	INDEX idx_key1 (key1),
	UNIQUE INDEX idx_key2(key2),
	INDEX idx_key3(key3),
	INDEX idx_key_part(key_part1, key_part2, key_part3)
)ENGINE=INNODB CHARSET=utf8

ストアド関数を作成する

-- 函数返回随机字符串
DELIMITER //

CREATE FUNCTION `rand_string`(n INT) RETURNS varchar(255) CHARSET utf8mb4
BEGIN 
	DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
	DECLARE return_str VARCHAR(255) DEFAULT '';
	DECLARE i INT DEFAULT 0;
	WHILE i < n DO 
       SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
       SET i = i + 1;
    END WHILE;
    RETURN return_str;
END //
DELIMITER ;


まず、信頼関数の変数 log_bin_trust_function_creators が 1 であることを確認します。

SELECT @@log_bin_trust_function_creators variable;

SET GLOBAL log_bin_trust_function_creators = 1;

ストアドプロシージャ

s1 テーブルと s2 テーブルにデータを追加するストアド プロシージャ

DELIMITER //
CREATE PROCEDURE insert_s1 (IN min_num INT (10), IN max_num INT(10))
BEGIN
	DECLARE i INT DEFAULT 0;
	SET autocommit = 0;
	REPEAT
	SET i = i + 1;
	INSERT INTO s1 VALUES(
		(min_num + i),
		rand_string(6),
		(min_num + 30* i + 5),
		rand_string(6),
		rand_string(10),
		rand_string(5),
		rand_string(10),
		rand_string(10)
	);
	UNTIL i = max_num
	END REPEAT;
	COMMIT;
END //
DELIMITER;



DELIMITER //
CREATE PROCEDURE insert_s2 (IN min_num INT (10), IN max_num INT(10))
BEGIN
	DECLARE i INT DEFAULT 0;
	SET autocommit = 0;
	REPEAT
	SET i = i + 1;
	INSERT INTO s1 VALUES(
		(min_num + i),
		rand_string(6),
		(min_num + 30* i + 5),
		rand_string(6),
		rand_string(10),
		rand_string(5),
		rand_string(10),
		rand_string(10)
	);
	UNTIL i = max_num
	END REPEAT;
	COMMIT;
END //
DELIMITER;

ストアド プロシージャを実行してデータを追加する

CALL insert_s1(10001, 10000);
CALL insert_s2(10001, 10000);

出力列の説明を説明します

列名 説明する
ID 大規模なクエリ内の各 SELECT キーワードは一意の ID に対応します
選択タイプ SELECT キーワードはクエリのタイプに対応します
テーブル テーブル名
パーティション 一致するパーティション情報
タイプ 単一テーブルへのアクセス方法
possible_keys 使用できるインデックス
実際に使用されるインデックス
key_len 実際に使用されるインデックスの長さ
参照 インデクス列等価クエリを使用する場合、インデクス列と等価一致するためのオブジェクト情報
読み取られるレコードの推定数
フィルタリングされた テーブルを検索条件で絞り込んだ後に残っているレコード数の割合
余分な 追加情報

1 ID

id、大規模なクエリ ステートメントでは、各 SELECT キーワードが一意の ID に対応するため、複数の選択キーワードがあり、複数の ID が存在します。

EXPLAIN SELECT * FROM s1

EXPLAIN SELECT * FROM s1 INNER JOIN s2

上記 2 つの SQL には選択が 1 つだけあるため、ID は 1 つだけです

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a'

サブクエリには 2 つの select があるため、2 つの id1 と 2 に対応します。

クエリ オプティマイザーは、サブクエリを含むクエリ ステートメントを書き換える場合があります

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a')

サブクエリを確認した後、オプティマイザは、複雑さを軽減するために、サブクエリが複数テーブル結合になる可能性があると判断します (O(n^2) -> O(n))。

SELECT * FROM s1, s2 ON s1.key1 = s2.key2 WHERE s2.common_field = 'a'

書き換えられた SQL は選択になるため、クエリ結果は ID のままになります。

しかし、s2がkey1をチェックすると以下のようになります。

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE common_field = 'a')

UNION重複排除

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

Union は重複排除操作により中間テーブルを使用するため、table<union, 1, 2> が存在します。

しかし、ここの一時テーブルにも id = 3 があり、Master Kong のビデオを視聴することはできません。バージョンの問題でしょうか? つまり、中間テーブルでも選択が実行されます。

重複排除なしで UNION ALL を使用する場合は、次のようになります。

EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;

概要:

  • ID が同じ場合、同じクエリグループとみなされ、上から下の順に実行されます。
  • 異なる場合は、ID が大きい方が優先され、最初に実行されます。
  • ID の数は独立したクエリを示し、SQL 内のクエリが少ないほど良いことになります。

2 タイプの選択

大きな クエリには複数の選択キーワードを含めることができ、各選択キーワードは小さなクエリ ステートメントを表し、各小さなクエリには接続操作用の複数のテーブルが含まれ、各テーブルは EXPLAIN クエリ プランのレコードの場合、同じ選択を持つテーブルの場合に対応します。キーワード、それらの ID は同じです

select_type: SELECT キーワードはクエリのタイプに対応します。つまり、小さなクエリの select_type 属性がわかっていれば、大規模なクエリにおけるこの小さなクエリの役割と機能を知ることができます。

一般的な select_types:

  • SIMPLE : UNION またはサブクエリを含まないクエリは SIMPLE タイプとみなされます。

  • UNIONPRIMARYUNION RESULT : UNION および UNION ALL を含むステートメントの場合、左端のクエリの select_type が PRIMARY で、残りは UNION で、一時テーブルの選択が UNION RESULT であることを除き、いくつかの小さなクエリで構成されます。

  • SUBQUERY : サブクエリを含むクエリ ステートメントを半結合メソッドに変換できず (つまり、オプティマイザがサブクエリをテーブル接続として最適化する)、サブクエリが相関サブクエリ (つまり、外部テーブル)の場合、サブクエリの最初の選択キーワードによって表されるクエリの select_type は SUBQUERY です。

  • Explain select * from s1 where key1 in (s2 から key1 を選択) または key3 = 'a'

  • まず、このサブクエリは相関サブクエリではないため、この SQL をテーブル結合用の SQL に最適化できますか?

  • select * from s1 INNER JOIN s2 on s1.key1 = s2.key1

  • 答えは「いいえ」です。2 つの SQL は異なります。たとえば、s1 テーブルには key1 値があり、s2 テーブルには重複する key1 値が 2 つあります。最初のステートメントは 1 回だけ一致します。 2 番目のステートメント SQL は等号であるため、この場合は 2 回一致するため、2 つの SQL によって得られる結果は完全に異なります。そのため、この SQL では 2 つの select が使用され、id が 2 つあり、1 つの select がプライマリになります。 , サブクエリのselectはsubqueryです。

  • DEPENDENT SUBQUERY : サブクエリを含むクエリ ステートメントは半結合メソッドに変換できませんが、サブクエリに外観が含まれる場合、つまり相関サブクエリである場合、サブクエリ select_type の最初の選択キーワードで表されるクエリは次のようになります。従属サブクエリ

  • EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 from s2 WHERE s1.key2 = s2.key2) OR key3 = 'a'

  • select_type が DEPENDENT SUBQUERY のクエリは複数回実行される可能性があります

  • DEPENDENT UNION : UNION や UNION ALL を含む大きなクエリで、左端の小さなクエリを除き、各小さなクエリが外側のクエリに依存する場合、残りのクエリの select_type は DEPENDENT UNION になります

  • EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b')

  • 2 番目のサブクエリ UNION が DEPENDENT を追加したものは、外観が使用されているため理解しやすいです。

  • しかし、なぜ最初のサブクエリは外部テーブルを使用せずに DEPENDENT SUBQUERY も使用するのでしょうか?

  • これは、オプティマイザによる次の変更が原因です。

  • where が存在する (s1.key1 = s2.key1 ...)、これは相関サブクエリになりますが、なぜこれが行われるのかわかりません。

  • DERIVED : 派生テーブルに対応するサブクエリの select_type は DERIVED です

  • EXPLAIN SELECT * FROM (SELECT key1, count(*) AS c FROM s1 GROUP BY key1) ASderive_s1 WHERE c > 1

  • つまり、ID 2 の派生テーブル

  • MATERIALIZED (具体化): クエリ オプティマイザーがサブクエリ ステートメントを実行し、サブクエリを外部クエリに接続することを選択すると、サブクエリに対応する select_type は MATERIALIZED になります。

  • EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2)

  • SELECT key1 FROM s2 の結果は 1 つずつレコードになり、外部テーブルに接続されます。これらのレコードはマテリアライズドテーブルと呼ばれ、クエリメソッドは MATERIALIZED です。

  • 外部選択は、サブクエリによって形成された実体化されたテーブルを通常のテーブルとして直接扱い、クエリ方法はシンプルです

  • これは、上記の非相関サブクエリに少し似ています。an または key3 = 'a' を追加すると、非相関サブクエリは実体化されたテーブルになります。? ?

  • EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) または key3 = 'a'

3テーブル

table、テーブルの名前

  • クエリされたレコードの各行は 1 つのテーブルに対応します

  • EXPLAIN SELECT * FROM s1

  • EXPLAIN SELECT * FROM s1、s2

  • 2 つのレコードは同じ大きなクエリ ステートメント (選択が 1 つだけ) に属しているため、ID が同じであることがわかります。

  • そして、s2はs1の前にランク付けされているため、s2が駆動テーブル、s1が被駆動テーブルになります(SQLの順序はオプティマイザによって最適化および変更される可能性があるため、SQL文に従って判断することはできません)

4つのパーティション

  • パーティション化されたテーブルのヒット状況を表します。パーティション化されていないテーブルの場合、値は NULL です。通常、クエリ ステートメントの実行プランのパーティション列の値も NULL です。

5型

実行プランのレコードは、テーブルに対してクエリを実行するときの MySQL のアクセス方法を表し、アクセス タイプとも呼ばれます。ここではそれがタイプです。たとえば、タイプが ref の場合、テーブル名mysql は ref メソッドを使用して、変更されたレコードのテーブルをクエリします

完全なアクセス方法は次のとおりです: system > const > eq_ref > ref > fulltext > ref_or_null > Index_merge > unique_subquery > Index_subquery > range > index > all、フロントが高いほど効率が高くなります

SQL パフォーマンス最適化の目標: 少なくとも range レベルに達すること。要件は ref レベル、できれば const レベルです。

  • system :テーブル内にレコードが 1 つだけあり、MyISAM や Memory など、テーブルで使用されるストレージ エンジンの統計が正確である場合、テーブルのアクセス方法は system です。

  • CREATE TABLE t(i INT) ENGINE=MYISAM; t 値に挿入(1); EXPLAIN SELECT * FROM t

  • ストレージ エンジンの統計は正確です。これは、たとえば、MyISAM ストレージ ストレージ エンジンによって記録されたレコードの数を意味します。

  • システムは最高パフォーマンスのケースです

  • さらにレコードを追加するとそれがすべてとなり、InnoDB では 1 つのデータでもすべてになります

  • 同時に、INNODB のアクセス数()のデータもすべて

  • CREATE TABLE tt(i INT) ENGINE=INNODB; tt VALUES(1) に挿入します。EXPLAIN SELECT count(*) FROM tt

  • const :主キーまたは唯一の副インデックスが定数と一致する場合、単一テーブルへのアクセスは定数のレベルを示す const になります。

  • EXPLAIN SELECT * FROM s1 WHERE id = 10005; EXPLAIN SELECT * FROM s1 WHERE key2 = 10066;

  • key3 の場合はすべて

  • EXPLAIN SELECT * FROM s1 WHERE key3 = 1006;

  • これには、実際には、暗黙的な変換によって引き起こされるインデックスの無効化の問題が含まれます。key3 は varchar 型ですが、ここには数値があり、関数変換が実行されるため、インデックスの無効化はすべてのユーザーによってのみクエリできます。

  • eq_ref :クエリを接続するときに、主キーまたは唯一の副次インデックスの等価一致メソッドを介して駆動テーブルにアクセスする場合(主キーまたは唯一の副次インデックスが結合インデックスの場合、インデックスの各列は結合一致する必要があります) )、駆動されるテーブルのアクセス方法は eq_ref です。

  • EXPLAIN SELECT * from s1 INNER JOIN s2 WHERE s1.key2 = s2.key2

  • key2 は一意制約のあるセカンダリ インデックスであるため、駆動テーブル s2 のアクセス方法は eq_ref です。

  • このうち、ref はクエリの値が指定されていることを示します。つまり、すべてのユーザーがクエリする s1 テーブルによって指定されます。

  • ref : 通常のセカンダリインデックスと定数の等価一致によってテーブルがクエリされる場合、テーブルのアクセス方法は ref である可能性があります。

  • EXPLAIN SELECT * FROM s1 WHERE key3 = 'CUTLVwqweqweq';

  • ここで、key3 は一意性制約のない通常のインデックスです。インデックス key3 が使用されていることがわかり、型は ref です。

  • ref_or_null : 通常のセカンダリ インデックスと定数の間の等価一致を通じてテーブルがクエリされるとき、値が null 値である可能性がある場合、テーブルのアクセス方法は ref_not_null になる可能性があります。

  • EXPLAIN SELECT * FROM s1 WHERE key3 = 'CUTLVwqweqweq' OR key3 IS NULL;

  • Index_merge : 場合によっては、単一テーブルへのアクセスで、Intersection、Union、Sort-Union を使用してクエリを実行できます。

  • EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key2 = 123131

  • key1 と key2 は両方ともインデックス列であり、Select キーワードで使用できるインデックスは 1 つだけです。そのため、ここではインデックスを仮想インデックスにマージする方法が使用されています。これは、2 つのインデックス ツリーをスキャンして主キーを取り出し、共用体を取得するのと同じです。そしてテーブルに戻ります

  • ただし、AND の場合、インデックスは 1 つだけ使用されます (セカンダリ インデックスはここだけです。つまり const です)。

  • EXPLAIN SELECT * FROM s1 WHERE key1 = 'rCLXEg' AND key2 = 10036

  • unique_subquery: IN サブクエリを含む一部のクエリ ステートメントの場合、クエリ オプティマイザーが In サブクエリ ステートメントを EXISTS サブクエリに変更することを決定し、サブクエリが主キーの同等の一致を使用できる場合、サブクエリのタイプは unique_subquery になります。

  • EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 WHERE s1.key1 = s2.key1) OR key3 = 'a'

  • range : インデックスを使用して特定の範囲間隔でレコードを取得する場合は、 range メソッドを使用できます。

  • EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c')

  • すべてはインデックスのない列の場合

  • Index : インデックス カバレッジが使用でき、すべてのインデックス レコードをスキャンする必要がある場合、テーブルのアクセス方法はインデックスです。

  • EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a'

  • キーには結合インデクスが使用されていることがわかりますが、一番左の接頭辞の原則に従って、検索条件が key_part1 の場合にのみインデクスが使用できます。これは、検索条件と選択戻り列がキーに関連付けられているためです。結合インデックスなので、他の列を見つけるためにテーブルに戻る必要がないため結合インデックスはすべてのインデックス レコードをスキャンします(チェックされた列はすべてインデックス上にあります)。

  • テーブルに戻らずに必要なデータを見つけることができます。これをインデックスカバレッジといいます。

  • 次に、別の列を追加します。

  • EXPLAIN SELECT key1, key_part2 FROM s1 WHERE key_part3 = 'a'

  • 結果は ALL です。ジョイント インデックス列には key1 情報がないため、テーブルに戻って key1 を確認する必要があります。

  • all : フルテーブルスキャン

6 possible_key 和キー

EXPLAIN ステートメントによって出力される実行プランでは、 possible_key は 単一テーブル クエリで使用されるインデックスを示します。一般的なクエリに関与するフィールドにインデックスがある場合、そのインデックスはリストされますが、必ずしも使用されるわけではありません。クエリによって。

キーは、クエリ オプティマイザーがさまざまなインデックスを使用した場合のクエリ コストを計算した後、使用するインデックスが最終的に決定されることを意味します。

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a'

key1 と key3 はどちらも通常のセカンダリ インデックスですが、key3 は同等の一致であるため、コストが比較的低いため、最終的な選択はインデックス key3 を使用することです。

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' OR key3 = 'a'

ここで OR に変更すると、前述の Index_merge マージ インデックスに発展します。つまり、2 つのインデックス ツリーの主キーが抽出されて結合され、クラスター化インデックスに統合されてテーブルの戻り操作が実行されます。

EXPLAIN SELECT key1, key3 FROM s1 WHERE key1 > 'z' OR key3 = 'a'

拡張するには、クエリ列がカバリング インデックスを使用できる (つまり、クエリ列の値がインデックス ツリーで見つかる) 場合でも、テーブルに返す必要があるため、2 つのクエリの実行プランは同じです:

7 Index_len (結合インデックス分析)

使用されるインデックスの実際の 。index_len の値が大きいほど、より良い値になります。

ここでの自分自身との比較は大きいほど良くなります。これは主にジョイント インデックス用です。ジョイント インデックスの長さが長いほど、クエリで読み取る必要があるデータ ページが減り、効率が高くなります。

EXPLAIN SELECT * FROM s1 WHERE id = 10005

理由 4 : id カラムが int 型であるため、実データは 4 バイトを占有し、同時に行形式の主キーは空ではないため、NULL 値リストは必要なく、固定長で保持されます。可変長フィールド長リストは必要ないため、4 になります。

EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;

key2 は int 型で、4 バイトを占有し、一意の制約がありますが、空である可能性があるため、行形式の null 値のリストは 1 バイト、合計 5 バイトを占有します。

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

まず、key1 は varchar(100) で、テーブルは utf8mb3 形式であるため、実際のデータ ストレージは (100 * 3) = 300 バイトを占有し、長さ自体は固定されているため、可変長フィールドの長さのリストは次のようになります。行フォーマットは 2 バイト、NULL を占有します。 値リストは 1 バイト、合計 303 バイトを占有します。

同様に、次のクエリの 1 つは 303、もう 1 つは 606 です。このとき、key_len の役割が反映されます。2 番目の SQL は最初の SQL よりもジョイント インデックスをより完全に使用します。

EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';

8参照

ref は、インデックス列等価クエリを使用する場合に、インデックス列と等価一致するオブジェクトの情報を示します。

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

key1 は通常のセカンダリ インデックスであるため、型は ref (唯一のセカンダリ インデックスは const)、同等の一致する型は定数であるため、ref 列の値は const になります。

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;

テーブル接続であるため選択 ID は 1 つだけであり、主キーによる接続であるため、2 番目のテーブルのアクセス方法の種類は eq_ref (通常のインデックスは ref)、同等の比較は列です。 s1 なので、ref は atguigu1.s2.id です

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);

key1 は通常のセカンダリ インデックスであるため、型は ref、等価比較の型は関数の戻り値であるため、ref 列の値は func になります。

9行

rows: 読み取られる推定レコード数。値が小さいほど良い

値が小さいほど、同じデータ ページ内にある可能性が高く、IO 回数が少なくなります。

10 件のフィルター済み (行分析と組み合わせた)

filtered: テーブルが条件によってフィルタリングされた後に残っているレコード数の割合を示します。値が大きいほど優れています。

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';

上記は、条件付きフィルタリング後、要件を 100% 満たすことを示しています。

値が大きいほど良い理由: 条件フィルタリング後のレコードが 40 件あると仮定します。フィルタリングされた値が 100% の場合、40 レコードがあります。フィルタリングされた値が 10% の場合、400 レコードがあります。40 件と比較します。読み取る必要があるレコードのデータ ページが少なくなる

また 、インデックスの単一テーブル スキャンを実行する場合は、対応するインデックスを満たす検索条件を推定するだけでなく、他の条件を満たすレコードの数も同時に計算する必要があります。

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'b';

上記の SQL と同様に、rows303 はインデックス列 key1 を満たすために読み取る必要があるレコードの推定数を示し、filtered は common_field フィールドを追加した後の合計の推定読み取りパーセンテージを示します。

単一テーブル クエリの場合 

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';

まず、複数テーブルの結合クエリは同じ選択 ID であり、次に結合条件は通常のセカンダリ インデックスであるため、駆動テーブルのアクセス タイプ type はall、駆動テーブルのアクセス タイプ type はref、および最終的に s1 テーブルが読み取られると推定されます。行数は10152で、条件付きフィルター処理の後、10152 * 10%が s2 と一致するため、1015 が s2 テーブルの実行数になります。

11 エクストラ

追加は、他の列での表示には適さないが非常に重要な追加情報を示すために使用されます。この追加情報を使用すると、mysql が特定のクエリ ステートメントをどのように実行するかをより正確に知ることができます

  • テーブルは使用されません: from 句がありません。つまり、テーブルは使用されません。

  • 説明選択 1

  • 不可能 where: where ステートメントは常に false

  • EXPLAIN select * FROM s1 WHERE 1 != 1

  • このように、テーブルは使用されておらず、とにかく条件が適切ではありません。

  • ここで、フルテーブルスキャンを使用してテーブルのクエリを実行します。文中にテーブルの検索条件があれば、Extraに表示されます。

  • EXPLAIN select * FROM s1 WHERE common_field = 'a'

  • common_field はインデックスのない共通フィールドであるため、タイプは all であり、Extra はステートメントが where を通じて実行されることを示します。

  • 一致する最小/最大行がありません クエリ リストに最小または最大の集計関数があるが、WHERE 条件を満たすレコードがない場合、追加情報の入力を求めるプロンプトが表示されます。

  • EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'adqwdqweqwe'

  • where 条件が満たされると (または where 条件がまったくない場合)、「最適化されたテーブルを選択」と表示され、最適化されたテーブルが選択されていることを示します。

  • EXPLAIN SELECT MIN(key1) FROM s1

  • インデックスを使用: インデックス カバレッジが発生した場合、つまりクエリと検索条件の列がすべて使用されているインデックス内にある場合、つまりテーブルに戻る必要はありません。

  • EXPLAIN SELECT key1 FROM s1 WHERE key1 = 'a'

  • 主キーがある場合、それはカバーインデックスでもあります

  • インデックス条件を使用: インデックス条件がプッシュダウンされる場合、次の SQL クエリを考慮してください。

  • EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 like '%a%'

  • この SQL の通常の実行シーケンスは次のようになります。まず idx_key1 のインデックス ツリーを使用して key1 > z のすべての主キー値をクエリし、ここで 385 レコードの主キーを見つけて、これらの主キーを次のように返します。クラスター化インデックス内のテーブル 他の列を含むデータを検索し、返される残りのフィルター条件を判断します。

  • インデックス条件のプッシュダウンは、特殊な場合に最適化されます。つまり、残りのフィルター条件がインデックス列に対するものである場合、テーブルに戻ってから判断する必要がなく、テーブルに戻る操作を軽減できます。 、しかし行はまだ 385 です

  • 結合バッファの使用: ブロックベースのネストされたループ アルゴリズム: 駆動テーブルがアクセス速度を高速化するためにインデックスを効果的に使用できない場合、mysql はアクセス速度を高速化するためにメモリ内に結合バッファ メモリ ブロックを割り当てます。

  • EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field

  • common_field はインデックスのない列です

  • notexistes: テーブルが接続されている場合、where 条件の駆動テーブルの列が null に等しく、この列に非 null 制約がある場合、Extra は「notowned」と表示します。

  • EXPLAIN SELECT * FROM s1 LEFT JOIN s2 on s1.key1 = s2.key1 WHERE s2.id IS NULL

  • 駆動テーブルの列である必要があることに注意してください。メイン駆動テーブルでこれが発生すると、直接不可能な場所が表示され、駆動テーブルが再度表示されなくなります。

  • Union(index_merge) を使用する: または 2 つのインデックス (前のタイプで説明した Index_merge) を使用します。このとき、2 つのインデックス ツリーによって検出された ID が結合されて、where 条件フィルタリング用のテーブルに返されます。

  • EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a'

  • ゼロリミット:リミットが0の場合

  • ファイルソート ファイルソート:

    • ソートでインデックスを使用できる状況がいくつかあります。

    • EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 10;

    • このクエリは、idx_key1 インデックスを使用して、key1 列の 10 レコード (インデックス列でソート) を直接フェッチし、レコードの主キー値を持つテーブルを返し、すべての列の値を取得します。ただし、多くの場合、ソート操作ではインデックスを使用できず、メモリ (レコード数が少ない場合) またはディスク上でのみソートできます。MySQL では、メモリまたはディスクでのこのソート方法を総称してファイル ソートと呼びます。

    • ただ、ここで理解できないところがあるのですが、なぜ制限を外したり、制限を大きくするとファイルソートになるのでしょうか?

    • EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 97;

    • 個人的な推測: 注意すべき点が 1 つあります。つまり、制限が増加すると行数も増加し、特に制限が 95 付近になると、突然大幅に増加します。これは次の理由によるものです: 制限が小さい場合、インデックスの順序で取得される主キーの値も比較的集中しています。このとき、テーブルの戻り操作もシーケンシャルクエリのレベルですが、制限が大きすぎる場合や、制限が存在しない場合でも、主キーの値は返されません。特に分散することになるため(key1のインデックス列に従ってソートされるため、key1が集中し、主キーの値が分散する)、このときのテーブルへの読み込み操作はランダム検索のレベルに相当し、したがって、クエリ オプティマイザーがコストを判断した後は、メモリまたはディスク内でファイルを直接並べ替える方が良いでしょう。

    • インデックスのないクエリの場合、当然ながらファイルのみを並べ替えることができます。

    • EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;

  • 一時的な使用: mysql が重複排除やソートなどの一部の機能を実行するときに、インデックスを効果的に使用できない場合は、インデックスを完成させるために内部一時テーブルを確立する必要がある場合があります。

  • EXPLAIN SELECT DISTINCT common_field FROM s1;

  • 実行計画に一時テーブルが存在することは良い兆候ではありません。一時テーブルの確立と保守には多大なコストが必要となるため、インデックスを使用して一時テーブルを置き換えることを試みる必要があります。


まとめ

  • Explain ではキャッシュは考慮されません (レコードのロード方法は考慮されず、SQL ステートメントのみが考慮されます)。
  • Explain は、クエリの実行時に mysql によって行われた最適化作業を表示できません
  • Explain では、クエリに対するトリガー、ストアド プロシージャ、またはユーザー定義関数の影響が表示されません。
  • 一部の情報は推定値であり、正確な値ではありません

Explain のさらなる使用法

Explain の 4 つの出力フォーマット

Explain の 4 つの出力形式: 従来の形式、Json 形式、ツリー形式、およびビジュアル形式

1 従来のフォーマット

つまり、上で使用した、クエリ プランを要約する EXPLAIN ステートメントです。

2 JSON形式

従来の EXPLAIN ステートメントの出力には、実行計画の品質を測定するための重要な属性であるコストが欠けていますJSON形式は、4つの形式の中で実行コスト情報を含む最も詳細な形式です。次に、従来の形式と JSON 形式の EXPLAIN を比較します。

EXPLAIN SELECT * FROM s1 INNER JOIN s2 on s1.key1 = s2.key2 WHERE s1.common_field = 'a'

EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 on s1.key1 = s2.key2 WHERE s1.common_field = 'a'
{
  "query_block": {
    "select_id": 1, // 原来的id
    "cost_info": {
      "query_cost": "1394.77" // 查询成本
    },
    "nested_loop": [
      {
        "table": {
          "table_name": "s1", // table
          "access_type": "ALL", // type
          "possible_keys": [
            "idx_key1"
          ],
          "rows_examined_per_scan": 10152, // rows
          "rows_produced_per_join": 1015, // rows * filtered
          "filtered": "10.00",
          "cost_info": {
            "read_cost": "937.93",
            "eval_cost": "101.52",
            "prefix_cost": "1039.45", // read + eval
            "data_read_per_join": "1M" // 读取的数据量
          },
          "used_columns": [ // 查询字段
            "id",
            "key1",
            "key2",
            "key3",
            "key_part1",
            "key_part2",
            "key_part3",
            "common_field"
          ],
          "attached_condition": "((`atguigudb1`.`s1`.`common_field` = 'a') and (`atguigudb1`.`s1`.`key1` is not null))" // 查询条件
        }
      },
      {
        "table": {
          "table_name": "s2",
          "access_type": "eq_ref",
          "possible_keys": [
            "idx_key2"
          ],
          "key": "idx_key2",
          "used_key_parts": [
            "key2"
          ],
          "key_length": "5",
          "ref": [
            "atguigudb1.s1.key1"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 1015,
          "filtered": "100.00",
          "index_condition": "(cast(`atguigudb1`.`s1`.`key1` as double) = cast(`atguigudb1`.`s2`.`key2` as double))",
          "cost_info": {
            "read_cost": "253.80",
            "eval_cost": "101.52",
            "prefix_cost": "1394.77",
            "data_read_per_join": "1M"
          },
          "used_columns": [
            "id",
            "key1",
            "key2",
            "key3",
            "key_part1",
            "key_part2",
            "key_part3",
            "common_field"
          ]
        }
      }
    ]
  }
}
  • read_cost: IO コスト行の CPU コスト * (1 - フィルタリングされた) レコードの 2 つの部分で構成されます。
  • eval_cost: 行数 * フィルター済み

3 ツリー形式

ツリー形式はバージョン 8.0.16 以降に導入された新しい形式で、主に各部分の関係と各部分の実行順序に基づいてクエリを実行する方法が記述されています。

EXPLAIN FORMAT=TREE SELECT * FROM s1 INNER JOIN s2 on s1.key1 = s2.key2 WHERE s1.common_field = 'a'
-> Nested loop inner join  (cost=1394.77 rows=1015)
    -> Filter: ((s1.common_field = 'a') and (s1.key1 is not null))  (cost=1039.45 rows=1015)
        -> Table scan on s1  (cost=1039.45 rows=10152)
    -> Single-row index lookup on s2 using idx_key2 (key2=s1.key1), with index condition: (cast(s1.key1 as double) = cast(s2.key2 as double))  (cost=0.25 rows=1)

4 視覚的な出力

MySQLワークベンチをインストールする必要があります

警告表示の使用

Explain ステートメントを使用してクエリ ステートメントの実行プランを表示した後、Show warnings を使用してクエリ プランに関連する次のような拡張情報を表示することもできます。

EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 on s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;

通常、s2 left join s1 を使用すると、s2 が駆動テーブル、s1 が駆動テーブルになるはずですが、オプティマイザが実行を駆動するときに 2 つのテーブルを次のように判断するため、実際には実行計画が逆になっていることがわかります。テーブルのコストを考慮すると、SQL が最適化されます (where ステートメントは s2 用です)。show warnings を使用すると、この最適化を確認できます。

mysql> show warnings \G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `atguigudb1`.`s1`.`key1` AS `key1`,`atguigudb1`.`s2`.`key1` AS `key1` from `atguigudb1`.`s1` join `atguigudb1`.`s2` where ((`atguigudb1`.`s1`.`key1` = `atguigudb1`.`s2`.`key1`) and (`atguigudb1`.`s2`.`common_field` is not null))
1 row in set (0.00 sec)
SQL 复制 全屏

不自然に見えるのは、次のようなことです。

select s1.key1, s2.key1
from s1 join s2
where s1.key1 = s2.key1 and s2.common_field is not null;

おすすめ

転載: blog.csdn.net/dyuan134/article/details/130242418