MySQL Optimizer Analysis——Join types
Louis Hust
0 前言
通过EXPLAIN查看执行计划,其输出中较为重要的列为type列,此列决定了操作此表的类型,在MySQL内部,此列对应了 enum join_type。这里分别针对Manual中列举出来的各种不同的type,进行实例显示,让大家更加明确的了解各个type的实际意义, 有利于加深大家对explain输出的理解。
由于MySQL5.6对Optimizer的代码进行了重构,条理上更加清晰,故本实验的环境是:
Server version: 5.6.6-m9-debug-log Source distribution
0 Join Type实例讲解
0.0 system
Manual上的说法是表只有一行数据即为system类型,说的过于模糊,因为我试过innodb的表(默认引擎),全表查询根本不是system, 这不科学,经过代码的查看,发现并没这么简单,要想join type为system,有两点要求:
- 表中数据行数小于等于1
- 表的存储引擎为MyISAM,HEAP或ARCHIVE
第一条和manual一致,第二条是通过代码查看发现的,只有上述的三种引擎,其ha_table_flag才包含HA_STATS_RECORDS_IS_EXACT, 这个标志表示统计信息中统计的行数是精确的,不是模糊的。此标志决定了system类型的判断,点到为止, 具体的可以查看代码make_join_statistics。下面给出一个实例:
mysql> create table t1(c1 int, c2 int) engine = myisam; Query OK, 0 rows affected (0.12 sec) mysql> insert into t1 values(1,1); Query OK, 1 row affected (0.03 sec) mysql> explain select * from t1; +----+-------------+-------+--------+---------------+------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+------+---------+------+------+-------+ | 1 | SIMPLE | t1 | system | NULL | NULL | NULL | NULL | 1 | NULL | +----+-------------+-------+--------+---------------+------+---------+------+------+-------+ 1 row in set (0.00 sec)
0.0 const
此种类型的join type表示最多只有一个匹配的行,优化器判断一个表为const后,会读取相应的行,在之后的优化过程中会直接用读取出的常量代替 语句中引用到的此表的个列。当出现主键或唯一键和常量进行等值比较时,便会产生const。
mysql> create table t1(c1 int primary key, c2 int unique); Query OK, 0 rows affected (0.26 sec) mysql> insert into t1 values(1,1),(2,2),(3,3); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> explain select * from t1 where c1=2; +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | t1 | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 1 row in set (0.00 sec) mysql> explain select * from t1 where c2=2; +----+-------------+-------+-------+---------------+------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+------+---------+-------+------+-------------+ | 1 | SIMPLE | t1 | const | c2 | c2 | 5 | const | 1 | Using index | +----+-------------+-------+-------+---------------+------+---------+-------+------+-------------+ 1 row in set (0.01 sec)
0.0 eq_ref
eq_ref类型,首先要确定的是它是一种ref,即reference,在ref属性上又添加了等值(eq)的属性,即为eq_ref,这种类型 是较为常见的一种比较好的join type。用于根据primary key或者unique not null索引进行等值判断,当然不再是和const进行比较, 如果和const进行比较,那么就是const类型的表了。 而是和前面一张表的一条记录进行比较(MySQL的join是nest loop类型的,且是left deep树,故表的join是从左到右一条一条记录进行匹配的)。
mysql> create table t1(c1 int, c2 int); Query OK, 0 rows affected (0.21 sec) mysql> insert into t1 values(1,1),(2,2); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> create table t2(c1 int primary key, c2 int unique not null); Query OK, 0 rows affected (0.21 sec) mysql> insert into t2 values(1,1), (2,2), (3,3); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.12 sec) mysql> explain select * from t1, t2 where t1.c1=t2.c1; +----+-------------+-------+--------+---------------+---------+---------+-----------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+-----------+------+-------------+ | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 2 | Using where | | 1 | SIMPLE | t2 | eq_ref | PRIMARY | PRIMARY | 4 | opt.t1.c1 | 1 | NULL | +----+-------------+-------+--------+---------------+---------+---------+-----------+------+-------------+ 2 rows in set (0.00 sec) mysql> explain select * from t1, t2 where t1.c1=t2.c2; +----+-------------+-------+--------+---------------+------+---------+-----------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+------+---------+-----------+------+-------------+ | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 2 | Using where | | 1 | SIMPLE | t2 | eq_ref | c2 | c2 | 4 | opt.t1.c1 | 1 | Using index | +----+-------------+-------+--------+---------------+------+---------+-----------+------+-------------+ 2 rows in set (0.00 sec)
0.0 ref
ref相比叫eq_ref少了eq,即不需要等值的结果只有一行,但是还是需要reference,可以和const直接进行比较,也可以根据reference table中的列进行比较,即可以根据索引进行匹配,但匹配结果不许要唯一。
mysql> create table t1(c1 int, c2 int); Query OK, 0 rows affected (0.17 sec) mysql> insert into t1 values(1,1), (2,2); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> create table t2(c1 int, c2 int, primary key(c1,c2)); Query OK, 0 rows affected (0.29 sec) mysql> insert into t2 values(1,1), (2,2); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> explain select * from t1,t2 where t1.c1=t2.c1; +----+-------------+-------+------+---------------+---------+---------+-----------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-----------+------+-------------+ | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 2 | Using where | | 1 | SIMPLE | t2 | ref | PRIMARY | PRIMARY | 4 | opt.t1.c1 | 1 | Using index | +----+-------------+-------+------+---------------+---------+---------+-----------+------+-------------+ 2 rows in set (0.00 sec)
0.0 ref_or_null
ref_or_null和ref相似,但是多了null的判断,where条件中可以对相应的索引key is null的判断。
mysql> create table t1(c1 int, c2 int, key(c1, c2)); Query OK, 0 rows affected (0.42 sec) mysql> explain select * from t1 where c1=1 or c1 is null; +----+-------------+-------+-------------+---------------+------+---------+-------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------------+---------------+------+---------+-------+------+--------------------------+ | 1 | SIMPLE | t1 | ref_or_null | c1 | c1 | 5 | const | 2 | Using where; Using index | +----+-------------+-------+-------------+---------------+------+---------+-------+------+--------------------------+ 1 row in set (0.00 sec)
0.0 index_merge
index_merge是针对一个表的,根据where条件,需要利用到多个index分别进行查询,然后进行merge,merge操作就包括三种: intersect,union和sort_union。
mysql> create table t1(c1 int, c2 int, key(c1), key(c2)); Query OK, 0 rows affected (0.21 sec) mysql> delimiter / mysql> drop procedure if exists p1; -> / Query OK, 0 rows affected, 1 warning (0.03 sec) mysql> create procedure p1(count int) -> begin -> set @i=1; -> repeat -> set @i=@i+1; -> insert into t1 values(@i,@i); -> until @i > count -> end repeat; -> end -> / Query OK, 0 rows affected (0.03 sec) mysql> call p1(1000); -> / Query OK, 1 row affected (1.00 sec) mysql> commit; -> / Query OK, 0 rows affected (0.36 sec) ql> delimiter ; mysql> explain select * from t1 where c1=100 or c2 = 100; +----+-------------+-------+-------------+---------------+-------+---------+------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------------+---------------+-------+---------+------+------+---------------------------------+ | 1 | SIMPLE | t1 | index_merge | c1,c2 | c1,c2 | 5,5 | NULL | 2 | Using union(c1,c2); Using where | +----+-------------+-------+-------------+---------------+-------+---------+------+------+---------------------------------+ 1 row in set (0.00 sec)
由于计划的选择需要统计IO和CPU代价,故需要初始化一些数据,才能使得index_merge的代价相对其他的计划是代价最小的。
0.0 range
range即范围查询,当然也需要利用到索引,主索引或者二级索引。
mysql> create table t1(c1 int primary key, c2 int); Query OK, 0 rows affected (0.18 sec) mysql> explain select * from t1 where c1 > 10; +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 1 | Using where | +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+ 1 row in set (0.01 sec)
0.0 index
二级索引上的全索引扫描,比主索引的全表扫描效率稍好,因为二级索引的I/O代价相对主索引较小。
mysql> create table t1(c1 int, c2 int, c3 varchar(100), key(c1, c2)); Query OK, 0 rows affected (0.23 sec) mysql> explain select c1,c2 from t1 where c2 > 10; +----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+ | 1 | SIMPLE | t1 | index | NULL | c1 | 10 | NULL | 1 | Using where; Using index | +----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+ 1 row in set (0.00 sec)
0.0 all
all表示主索引全表扫描,是性能最差的,如果计划中看到有大表的ALL,尽量通过建立二级索引或者一些参数设置,改变其执行计划。
mysql> drop table t1; Query OK, 0 rows affected (0.08 sec) mysql> create table t1(c1 int primary key, c2 int unique, c3 int) -> ; Query OK, 0 rows affected (0.25 sec) mysql> explain select * from t1 where c3 = 100; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec)
File translated from
TEX
by
TTH,
version 4.03.
On 29 Dec 2012, 11:12.