MySQL パフォーマンスの最適化 2 説明 詳細な説明

Explain ツールの概要

EXPLAIN キーワードを使用して、オプティマイザーをシミュレートして SQL ステートメントを実行し、クエリ ステートメントまたは構造のパフォーマンスのボトルネックを分析します。

select ステートメントの前に Explain キーワードを追加すると、MySQL はクエリにフラグを設定し、クエリを実行すると、この SQL を実行する代わりに実行計画情報が返されます。

注: from にサブクエリが含まれている場合でも、サブクエリは実行され、結果は一時テーブルに配置されます。

1.1 分析例の説明

このブログで使用されている MySQL のバージョンは 5.7 です。
公式ドキュメントを参照してください: https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

#示例表:
2 DROP TABLE IF EXISTS `actor`;
3 CREATE TABLE `actor` (
4 `id` int(11) NOT NULL,
5 `name` varchar(45) DEFAULT NULL,
6 `update_time` datetime DEFAULT NULL,
7 PRIMARY KEY (`id`)
8 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
9
10 INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (1,'a','2017‐12‐22
15:27:18'), (2,'b','2017‐12‐22 15:27:18'), (3,'c','2017‐12‐22 15:27:18');
11
12 DROP TABLE IF EXISTS `film`;
13 CREATE TABLE `film` (
14 `id` int(11) NOT NULL AUTO_INCREMENT,
15 `name` varchar(10) DEFAULT NULL,
16 PRIMARY KEY (`id`),
17 KEY `idx_name` (`name`)
18 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
19
20 INSERT INTO `film` (`id`, `name`) VALUES (3,'film0'),(1,'film1'),(2,'film2');
21
22 DROP TABLE IF EXISTS `film_actor`;
23 CREATE TABLE `film_actor` (
24 `id` int(11) NOT NULL,
25 `film_id` int(11) NOT NULL,
26 `actor_id` int(11) NOT NULL,
27 `remark` varchar(255) DEFAULT NULL,
28 PRIMARY KEY (`id`),
 #建立联合索引
29 KEY `idx_film_actor_id` (`film_id`,`actor_id`)
30 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
31
32 INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`) VALUES (1,1,1),(2,1,2),(3,2,1);
 explain select * from actor;

ここに画像の説明を挿入

クエリの各テーブルは 1 行を出力します。結合クエリで接続された 2 つのテーブルがある場合は、2 行が出力されます。

1.2 2 つのバリアントを説明する

Explain extended は、
explain に基づいて追加の照会最適化情報を提供します。その直後に、show warnings コマンドを使用して最適化されたクエリ ステートメントを取得し、オプティマイザーが何を最適化するかを確認できます。さらに、半分の比率値であるフィルター処理された列があります。rows *filtered/100 は、explain の前のテーブルに接続される行数を推定できます (前のテーブルは、explain の id 値を参照します。現在のテーブル ID 値サーフェスよりも小さい)。

# 5.7版本后就不需要加extended了,加这个关键字会多显示partitions和filtered两列
# partitions显示表是否有分区
explain extended select * from film where id = 1;
show warnings;

ここに画像の説明を挿入

ここに画像の説明を挿入
最適化された SQL は実行できない可能性がありますが、最適化の方法を大まかに示しているだけです。

Explain と比較すると、Explain Partitions には
パーティション フィールドが 1 つ多くあり、クエリがパーティション テーブルに基づいている場合は、クエリがアクセスするパーティションが表示されます。

1.3 Explain のすべての列の意味

次に、Explain の各列の情報を表示します。

1.3.1 id列

id 列の番号は、select の通し番号で、select が複数あり、id が複数あり、id の順序は select が表示される順序で増加します。

id 列が大きいほど、実行の優先度が高くなります。id が同じ場合、上から下に実行されます (つまり、id が一意ではありません)。id が NULL の場合、最後に実行されます。

1.3.2 select_type列

select_type は、対応する行が単純なクエリか複雑なクエリかを示します。

  1. simple: シンプルなクエリ。クエリにサブクエリとユニオンが含まれていません
 explain select * from film where id = 2;

ここに画像の説明を挿入

  1. プライマリ: 複雑なクエリで最も外側の選択
  2. subquery (subquery): select に含まれるサブクエリ (from 句には含まれない)
  3. 派生 (派生クエリ): from 句に含まれるサブクエリ。MySQL は、派生テーブルとも呼ばれる一時テーブルに結果を格納します(派生の英語の意味)。

この例を使用して、プライマリ、サブクエリ、および派生型を理解してください

 #关闭mysql5.7新特性对衍生表的合并优化
 set session optimizer_switch='derived_merge=off'; 
 explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;

ここに画像の説明を挿入
ここに画像の説明を挿入

 #还原默认配置
 set session optimizer_switch='derived_merge=on'; 
  1. ユニオン: ユニオン内の 2 番目以降の選択
explain select 1 union all select 1;

ここに画像の説明を挿入

1.3.3 table列

この列は、Explain の行がどのテーブルにアクセスしているかを示します。
from 句にサブクエリがある場合、テーブル列の<derivenN>形式は に。これは、現在のクエリが id=N のクエリに依存していることを示しているため、id=N のクエリが最初に実行されます。
ユニオンがある場合、 UNION RESULT のテーブル列の値<union1,2>1 と 2 は、ユニオンに参加している選択行 ID を表します。

1.3.4 type列

この列は、アソシエーション タイプまたはアクセス タイプ、つまり、MySQL がテーブル内の行を検索する方法を決定する方法、およびデータ行レコードを検索するおおよその範囲を表します。

システム > const > eq_ref > ref > 範囲 > インデックス > ALL 一般的に言えば

、クエリが範囲レベル、できれば ref に到達することを確認する必要があります。

NULL : Mysql は、クエリ ステートメントを実行するときに最適化フェーズと実行フェーズに分けられます。mysql は最適化フェーズでクエリ ステートメントを分解でき、実行フェーズではテーブルやインデックスにアクセスする必要はありません。たとえば、インデックス列の最小値の選択は、インデックスのみを検索することで実行できます (インデックス ツリー B+ ツリーでツリーの左端のリーフ ノードを直接検索します。テーブルを検索する必要はありません)。実行中にテーブルにアクセスする

explain select min(id) from film;

ここに画像の説明を挿入
const, system : mysql は、クエリの特定の部分を最適化し、それを定数に変換できます (show warnings の結果を確認できます)。主キーまたは一意キーのすべての列が定数と比較されるため、テーブルに一致する行が最大で 1 つある場合、読み取りは 1 回であり、速度は比較的高速です。system はconst の特殊なケースです。テーブルで 1 つのタプルのみが一致する場合、それは system です。

 explain extended select * from (select * from film where id = 1) tmp;

ここに画像の説明を挿入

# 这个子查询根据主键查询,得到的是一个常量(const),效率非常高
select * from film where id = 1

外側のレイヤーの選択の場合、クエリはテーブルであり、このテーブルにはレコードが1行しかないため、効率が高く、システムレベルであり、最高です。

show warnings;

ここに画像の説明を挿入
eq_ref : 主キーの関連付け; 主キー (主キー) または一意のキー (一意のキー) インデックスのすべての部分が接続によって使用され、条件を満たす最大 1 つのレコードが返されます。これはおそらく const 以外の最適な結合タイプであり、単純な選択クエリにはこのタイプがありません。

> explain select * from film_actor left join film on film_actor.film_id = film.id;

ここに画像の説明を挿入
ref : eq_ref と比較すると、一意のインデックスは使用されませんが、通常のインデックスまたは一意のインデックスの部分的なプレフィックスが使用されます. インデックスは特定の値と比較する必要があり、複数の適格な行が見つかる場合があります.

  1. 単純な選択クエリ、名前は通常のインデックス (一意でないインデックス)
select * from film where name = 'film1';

ここに画像の説明を挿入2. アソシエーション テーブル クエリ、idx_film_actor_id は、film_id と actor_id の結合インデックスです。ここでは、film_actor の左側の接頭辞 film_id が使用されます。

explain select film_id from film left join film_actor on film.id = film_actor.fi
lm_id;

ここに画像の説明を挿入
range : 通常、範囲スキャンは in()、 between 、> 、<、 >= などの操作で使用されます。インデックスを使用して、特定の範囲の行を取得します。

explain select * from actor where id > 1;

ここに画像の説明を挿入
index : 完全なインデックスをスキャンして結果を取得できます. 一般に, セカンダリ インデックスをスキャンします (主キー インデックスではありません). このスキャンはインデックス ツリーのルート ノードからすばやく検索するのではなく, インデックス ツリーのリーフ ノードを直接走査します.セカンダリ インデックスとスキャン、速度はまだ比較的遅いです.この種のクエリは一般的にカバリング インデックスを使用します(後述)。

explain select * from film;

チェックする結果がセカンダリ インデックスで取得できる場合は、セカンダリ インデックス
ここに画像の説明を挿入
ALLが優先されます。つまり、クラスター化インデックスのすべてのリーフ ノードをスキャンするフル テーブル スキャンです。通常、これは最適化するためにインデックスを増やす必要があります

explain select * from actor;

ここに画像の説明を挿入

1.3.5 possible_keys列

この列には、クエリがルックアップに使用できるインデックスが表示されます。
説明すると、possible_keys は値を持っているかもしれませんが、キーが NULL を示しています. これは、テーブルにデータがあまりないためです. Mysql は、このクエリにはインデックスがあまり役に立たないと考えています (mysql は、テーブル全体をスキャンする方が速いと考えています) 、だから私はフルテーブルクエリを選びました。

列が NULL の場合、関連付けられたインデックスはありません。この場合、where 句をチェックして適切なインデックスを作成できるかどうかを確認し、explain を使用してその効果を確認することで、クエリのパフォーマンスを向上させることができます。

1.3.6 キー列

この列は、テーブルへのアクセスを最適化するために mysql が実際に使用するインデックスを示します。
インデックスが使用されていない場合、この列は NULL です。mysql に possible_keys 列のインデックスを強制的に使用または無視させたい場合は、クエリで forceindex を使用し、index を無視します。

1.3.7 key_len列

This column shows the number of bytes used by mysql in the index. この値を使用して、(結合) インデックス内のどの列が使用されているかを計算できます。
たとえば、film_actor テーブルの結合インデックス idx_film_actor_id は、2 つの int 列 film_id と actor_id で構成され、各 int は 4 バイトです。結果の key_len=4 から、クエリが最初の列である film_id 列を使用してインデックス ルックアップを実行していると推測できます。

# key_len=4
explain select * from film_actor where film_id = 2;
# 此时key_len=8
explain select * from film_actor where film_id = 2 and actor_id = 2;

ここに画像の説明を挿入
key_len の計算規則は次のとおりです。

  • String, char(n), varchar(n), 5.0.3 以降のバージョンでは、n はバイト数ではなく文字数を表し、utf-8 の場合、数字または文字は 1 バイトを占め、中国語は文字 3 バイト

    • char(n): 漢字格納長が3nバイトの場合
    • varchar(n): 漢字を格納する場合、長さは 3n + 2 バイトで、追加された 2 バイトは文字列の長さを格納するために使用されます。これは、
      varchar が可変長文字列であるためです。
  • 値のタイプ

    • tinyint: 1 バイト
    • smallint: 2 バイト
    • 整数: 4 バイト
    • bigint: 8 バイト
  • 時間の種類

    • 日付: 3 バイト
    • タイムスタンプ: 4 バイト
    • 日時: 8 バイト
  • フィールドが NULL を許可する場合、NULL かどうかを記録するために 1 バイトが必要です。

インデックスの最大長は 768 バイトです.文字列が長すぎる場合、mysql は左のプレフィックス インデックスと同様のプロセスを実行し、インデックス用の文字の前半を抽出します。

1.3.8 参照列

この列は、キー列に記録されたインデックスのテーブル ルックアップ値で使用される列または定数を示します。一般的なものは、const (定数)、フィールド名 (例: film.id) です。

1.3.9 rows列

この列は、 mysql が読み取りおよび検出するために見積もる行数です。これは、結果セット内の行数ではないことに注意してください。

1.3.10 余分な列

この列には追加情報が表示されます。一般的な重要な値は次のとおりです。

1.3.10.1 インデックスの使用: カバリング インデックスの使用

カバー インデックス定義: インデックスではなく、SQL を検索する方法です。検索する結果セットはすべてインデックス ツリー (カバー) にあります。つまり、目的の結果はインデックス ツリーを介して見つけることができます。 go back Table; mysql execution plan Explain の結果のキーはインデックスを使用します. select の後にクエリされたフィールドがこのインデックスのツリーから取得できる場合、この状況は一般にカバリング インデックスを使用していると言えます。は一般にエクストラの using インデックスです ;カバリング インデックスは一般に補助インデックスを対象としています.全体のクエリ結果は補助インデックスを介してのみ取得できます.補助インデックス ツリーを介して主キーを見つける必要はありません.主キーを使用して、主キー インデックス ツリー内の他のフィールド値を取得します。

explain select film_id from film_actor where film_id = 1;

ここに画像の説明を挿入

1.3.10.2 where の使用

where ステートメントを使用して結果を処理し、クエリ列がインデックスでカバーされていない

explain select * from actor where name = 'a';

ここに画像の説明を挿入

1.3.10.3 索引条件の使用

クエリ列はインデックスによって完全にはカバーされておらず、where 条件は先頭の列の範囲です。

explain select * from film_actor where film_id > 1;

ここに画像の説明を挿入

1.3.10.4 一時的な使用

mysql は、クエリを処理するために一時テーブルを作成する必要があります。このような場合、通常は最適化が必要ですが、最初に考えるべきことは、インデックスを使用して最適化することです。

  1. actor.name にはインデックスがなく、この時点で区別するために一時テーブルが作成されます
explain select distinct name from actor;

ここに画像の説明を挿入

  1. film.name は idx_name インデックスを確立しました。extra はこの時点でクエリを実行するときにインデックスを使用しており、一時テーブルは使用されていません。
explain select distinct name from film;

ここに画像の説明を挿入

1.3.10.5 ファイルソートの使用

インデックス ソートの代わりに外部ソートが使用されます。データが小さい場合は、メモリからソートされます。それ以外の場合は、ディスク上でソートする必要があります。この場合、通常、最適化のためにインデックスを使用することを検討する必要があります。

  1. actor.name はインデックスを作成しません。アクターのテーブル全体を参照し、並べ替えキーワード名と対応する ID を保存してから、名前を並べ替えて行レコードを取得します。
explain select * from actor order by name;

ここに画像の説明を挿入

  1. film.name は idx_name インデックスを確立しており、extra はこの時点でクエリを実行するときにインデックスを使用しています
explain select * from film order by name;

ここに画像の説明を挿入

1.3.10.6 最適化されたテーブルの選択

いくつかの集計関数 (max、min など) を使用して、インデックスに存在するフィールドにアクセスすることは、

explain select min(id) from film;

ここに画像の説明を挿入

セカンダリ インデックスのベスト プラクティス

#示例表:
2 CREATE TABLE `employees` (
3 `id` int(11) NOT NULL AUTO_INCREMENT,
4 `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
5 `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
6 `position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
7 `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
# 主键
8 PRIMARY KEY (`id`),
# 联合索引
9 KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
10 ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
11
12 INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
13 INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei',
23,'dev',NOW());
14 INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());

2.1 完全な値の一致

完全な値の一致: 結合インデックスのすべての列が使用されます

# name字段定义的类型是varchar(24):3N+2=3X24+2=74(上边有这个公式:3N+2)
 EXPLAIN SELECT * FROM employees WHERE name= 'LiLei';

ここに画像の説明を挿入

# 在上边的基础上(74)加int类型的4=78
EXPLAIN  SELECT * FROM employees WHERE name= 'LiLei' AND age = 22;

ここに画像の説明を挿入

# 效率最高;因为是全值匹配
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';

ここに画像の説明を挿入

2.2 左端のプレフィックス規則

複数の列にインデックスが付けられている場合は、左端のプレフィックス ルールに従う必要があります。インデックスの最左端の列から開始し、インデックス内の列をスキップしないクエリを参照します。
関節インデックスの順序:

KEY `idx_name_age_position` (`name`,`age`,`position`)

フレーズを確認します。

EXPLAIN SELECT * FROM employees WHERE name = 'Bill' and age = 31;
EXPLAIN SELECT * FROM employees WHERE age = 30 AND position = 'dev';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';

ここに画像の説明を挿入

2.3 インデックス列に対して操作を行わない

計算、関数、(自動または手動の) 型変換により、インデックス エラーが発生し、完全なテーブル スキャンに変わります。

# 走索引
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
# 不走索引,因为在索引树里没法寻找
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';

ここに画像の説明を挿入
Hire_time に通常のインデックスを追加します。

ALTER TABLE `employees` ADD INDEX `idx_hire_time` (`hire_time`) USING BTREE ;
# 不走索引,因为通过date函数后得到的结果集在索引树里不一定能找到
EXPLAIN select * from employees where date(hire_time) ='2018‐09‐30';

ここに画像の説明を挿入
日付範囲クエリに変換すると、次のインデックスを使用できます。

EXPLAIN select * from employees where hire_time >='2018‐09‐30 00:00:00' and hire_time <='2018‐09‐30 23:59:59';

ここに画像の説明を挿入
元のインデックスの状態を復元する

ALTER TABLE `employees` DROP INDEX `idx_hire_time`;

2.4 ストレージ エンジンは、インデックスの範囲条件の右側の列を使用できません

#三个字段的索引都走了
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
#只走了前两个索引:name字段相对等的前提下,age一定是有序的,但是age不相等的话,position就不是有序的了,所以position不走索引
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';

ここに画像の説明を挿入

2.5 カバリングインデックスを使ってみよう

インデックスのみにアクセスするクエリ (インデックス列にはクエリ列が含まれます)、select * ステートメントを削減します。

EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position='manager';

ここに画像の説明を挿入

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';

ここに画像の説明を挿入

2.6 等しくない (!= または <>)、not in、not exists を使用すると、Mysql はインデックスを使用できず、テーブル全体のスキャンが発生します。

< より小さい、> 大きい、<=、>= これらは、mysql の内部オプティマイザが、取得率やテーブル サイズなどの複数の要因に基づいて、全体としてインデックスを使用するかどうかを評価します。

EXPLAIN SELECT * FROM employees WHERE name != 'LiLei';

ここに画像の説明を挿入

2.7 はヌル、ヌルではない 一般的に、インデックスは使用できません

EXPLAIN SELECT * FROM employees WHERE name is null

ここに画像の説明を挿入

2.8 のようにワイルドカード ('$abc...') で始まる Mysql インデックスの失敗は、テーブル全体のスキャン操作になります。

EXPLAIN SELECT * FROM employees WHERE name like '%Lei'

ここに画像の説明を挿入

# 前边字符串是有序的,可以走索引
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'

ここに画像の説明を挿入
質問: '%string%' のようなインデックスが使用されていない問題を解決するにはどうすればよいですか?
a) カバリング インデックスを使用するには、クエリ フィールドがカバリング インデックス フィールドである必要があります。

EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';

ここに画像の説明を挿入
b) カバリング インデックスを使用できない場合は、検索エンジンを使用する必要がある場合があります

2.9 文字列が単一引用符で追加されていない場合、インデックスは失敗します

EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;

ここに画像の説明を挿入

2.10 以下またはまたはで使用

クエリに使用する場合、mysql は必ずしもインデックスを使用するとは限りません. mysql の内部オプティマイザは、検索率やテーブル サイズなどの複数の要因に基づいて、全体としてインデックスを使用するかどうかを評価します. 詳細については、範囲クエリの最適化を参照してください。

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';

ここに画像の説明を挿入

2.11 範囲クエリの最適化

age に単一値インデックスを追加する

ALTER TABLE `employees` ADD INDEX `idx_age` (`age`) USING BTREE ;
EXPLAIN select * from employees where age >=1 and age <=2000;

ここに画像の説明を挿入
インデックスを使用しない理由: mysql の内部オプティマイザは、検索率やテーブル サイズなどの複数の要因に基づいて、全体としてインデックスを使用するかどうかを評価します。たとえば、この例では、オプティマイザは、1 つのクエリに大量のデータがあるため、最終的にインデックスを使用しないことを選択する可能性があります。

最適化方法: 大きな範囲を複数の小さな範囲に分割できます

 explain select * from employees where age >=1 and age <=1000;
 explain select * from employees where age >=1001 and age <=2000;

ここに画像の説明を挿入
元のインデックスの状態を復元する

ALTER TABLE `employees` DROP INDEX `idx_age`;

3 つの MySQL インデックスの使用の概要

ここに画像の説明を挿入
likeKK%は = constant に相当し、%KKlike%KK%は range に相当します

‐‐ mysql5.7关闭ONLY_FULL_GROUP_BY报错
select version(), @@sql_mode;SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));

おすすめ

転載: blog.csdn.net/qq_33417321/article/details/121192241