-
Notifications
You must be signed in to change notification settings - Fork 55
Extra列值详解
xiaoboluo768 edited this page Jun 13, 2020
·
2 revisions
- EXPLAIN输出的Extra列包含有关MySQL如何解析查询的附加信息。 以下列出了在explain的Extra列中可能出现的值。 每个值同时还列出了JSON格式的输出对应的属性值。其中一些在json格式中有一个特定的属性。一些显示为message属性。如果你希望你的查询尽可能快地完成,请注意Using filesort和Using temporary的值,与之对应的在JSON格式的EXPLAIN输出中是Using_filesort和use_temporary_table属性。注:以下解释部分出现的SQL示例的employees和salaries表使用的是mysql的样例数据库employees库,下载链接:https://github.com/datacharmer/test_db,安装方法参考地址:https://dev.mysql.com/doc/employee/en/employees-installation.html
- Child of 'table' pushed join@1 (JSON: message text):表示此表在连接中被当作子表引用,可以向下推送到NDB内核,仅仅在pushed-down joins功能开启的MySQL cluster中使用,详情参见ndb_join_pushdown 系统变量描述部分
- const row not found (JSON property: const_row_not_found):对于使用类似SELECT ... FROM tbl_name的查询一张空表时会出现这个值,即表示虽然在执行计划中,显示使用const方式访问读取了数据表,但是实际上表中没有至少1行符合条件的记录。查看一下是不是你忘记了插入测试数据。
- Deleting all rows (JSON property: message):对于DELETE,一些存储引擎(例如MyISAM)支持一种以简单快速的方式删除所有表行的处理方法。 如果引擎使用到了此优化方法,将出现这个值。这个是从存储引擎的Handler角度来看的。在不带where条件的delete语句的执行计划中,经常会发现这个提示信息。它表示调用一次删除数据表所有记录的Handler功能(API),以前删除数据表记录时,要一句记录条数反复调用各存储引擎的Handler函数,而采用了Deleting all rows优化处理方式则只需要调用一次Handler函数,处理起来更加快速(注意:实测5.6.35,5.7.17,mariadb10.1.22的myisam,innodb,aria,memory引擎都不会出现这个提示,估计在早期版本中才会出现)
- Distinct (JSON property: distinct):查询select字段使用了distinct关键字。表示MySQL正在寻找去重的值,所以在发现第一个匹配的行之后停止寻找更多行以便对当前行进行合并
- 举个例子:explain select distinct d.dept_no from departments d,dept_emp de where de.dept_no=d.dept_no; 该语句实际上是需要访问d表的dept_no字段,而dept_no在d表与de表数据表中都存在,执行查询时会先将两个表的数据连接起来,然后再在结果集中进行distinct处理,这样dept_no就不会有重复的了
- FirstMatch(tbl_name) (JSON property: first_match):表示对于tbl_name表(tbl_name表是外表)的连接查询使用了简单的半连接的 FirstMatch 策略,该策略是mariadb 5.3与mysql5.6之后引入的针对子查询的优化策略,在这之前的版本中经常将子查询(IN subquery)转换为exists subquery类型的查询(即,IN-TO-EXISTS优化策略)。策略算法是先从外表数据表读取一行记录,然后检索内表的记录,直到在内表中检索到匹配外表条件值的记录为止。如果在子查询中1次即可查找到符合条件的记录,那么执行代价会非常低,如果子查询中不存在任何记录与外表的where条件匹配,那么执行代价可能会非常高,因为外表的每一行记录都会对内表进行全表扫描,直到找到一行匹配记录为止。 举个例子:
- explain select * from departments d where exists(select 1 from dept_emp de where de.dept_no=d.dept_no); 在5.7中该语句秒执行,但是并没有看到 FirstMatch的提示信息,在select_type列显示的是DEPENDENT SUBQUERY。在mariadb 10.1.22中,会使用MATERIALIZED查询类型来先把子查询(de表)的内容具体化(物化)成一个临时表,然后依次连接d表与临时表,再执行查询。这种效率要比使用FirstMatch()优化策略要低,使用MATERIALIZED查询类型时,时间大约为0.3S
- explain select * from departments where dept_no in(select dept_no from dept_emp); 该语句中,在mysql 5.6.x,mysql5.7.x版本的执行计划中会出现这个提示信息,但是在mariadb的执行计划中,仍然使用子查询MATERIALIZED的方式。
- Full scan on NULL key (JSON property: message):这发生在如果优化器无法使用索引查找访问方法,那么就把子查询优化作为一个后备策略。经常出现在类似col1 in (select col2 from ...)的WHERE条件查询中,若col1值为null,则条件最终变为null in (select col2 from ...),col1如果定义为允许为null时,必须进行全表扫描才能查询出结果(也可以改写语句加一个col1 is not null and col1 in (...),这样就等于显式告诉优化器col1不可能为null,就不会进行全表扫描,也不会在Extra中显示Full scan on NULL key信息,要注意,如果col1可能为null时,子查询中一定要指定where条件,否则会导致严重的性能问题),若col1不可能为null(col1定义为not null),则不会使用全表扫描。SQL标准将null定义为未知值,也定义了null的运算规则,根据规则定义,执行运算时需要对条件进行如下比较
- 若子查询拥有至少一条结果记录,则最终比较结果为null
- 若子查询未拥有任何结果记录,则最终比较结果为FALSE
- Impossible HAVING (JSON property: message):HAVING子句始返回false(不实际执行,而是通过数据表结构判断条件为不可能),表示不存在满足查询HAVING 子句的记录,举个例子
- explain select e.emp_no,count(*) from employees e where e.emp_no=10001 group by e.emp_no having e.emp_no is null; 在该查询语句中,因为e.emp_no是e表的主键,所以不可能出现e.emp_no is null的情况,那么执行计划中就会出现这个提示,在实际应用中如果出现这个提示,很可能是查询语句编写错误。
- Impossible WHERE (JSON property: message):WHERE子句始返回false(不实际执行,而是通过数据表结构判断条件为不可能),表示不存在满足查询WHERE 子句的记录,举个例子
- explain select * from employees where emp_no is null; 该语句中,emp_no列为表的主键,不可能为null,所以执行计划中出现 Impossible WHERE提示。跟 Impossible HAVING类似,很可能是因为查询语句编写错误导致
- Impossible WHERE noticed after reading const tables (JSON property: message):前面说了,Impossible 是不实际执行,而是通过数据表结构判断条件为不可能,但是有时候如果不实际执行就无法判断记录是否存在(如:explain select * from emp where emp_no=0),事实上只需要优化器执行一下查询计划,就知道该记录是否存在。那么实际执行过程中是怎样的呢?只需要执行查询的一部分即可,执行查询记录是否存在使用的是const类型查询,在一些join查询中还会先使用部分执行结果进行内部值替换(如:select * from emp as e where oe.first_name=(select ie.first_name from emp as ie where ie.emp_no=1)在执行计划查看时优化器会先把ie.emp_no=1对应的记录的oe.first_name值查询出来,然后把语句改写为select * from emp as oe where oe.first_name='xxx',然后如果xxx值不存在,可能就显示Impossible WHERE noticed after reading const tables值)。注意:在5.7之前单表简单查询中,如果值不存在会显示这个值,但是在5.7之后的版本,如果单表简单查询值不存在,Extra列显示的也是 no matching row in const table,而不是像5.7之前的版本需要在const类型的join查询中值不存在才显示 no matching row in const table
- LooseScan(m..n) (JSON property: message):使用半连接LooseScan策略。 m和n是key部分数字。mysql5.6和mariadb5.3开始引入的策略。用于优化子查询的众多策略之一。在in subquery类型的查询中,子查询的结果可能产生重复记录时,使用该优化方法。子查询的结果集去重后被用作驱动表,与外表进行连接查询。使用到该优化策略时,不需要使用临时表来存放结果集。举个例子(在mysql和mariadb中都需要关闭firstmatch和materialization才能引导查询优化器使用该策略:set optimizer_switch=default,optimizer_switch='firstmatch=off,materialization=off';):
- explain select * from departments where dept_no in(select dept_no from dept_emp); 该语句在mysql5.6.35,mysql5.7.17,mariadb 10.1.22版本中测试时,如果不关闭firstmatch和materialization策略,mysql使用的是FirstMatch策略,mariadb使用的是MATERIALIZED查询类型。如果都关闭firstmatch和materialization策略,mysql使用的是Start temporary; End temporary,mariadb使用的LooseScan策略。
- No matching min/max row (JSON property: message):没有满足查询条件的记录,例如SELECT MIN(...)FROM ... WHERE xx=value。前面说了,如果表中没有记录满足where条件,则应该出现类似Impossible xxx的Extra提示,但是如果查询使用了聚合函数max()/min(),且表中没有任何符合条件的记录时,则Extra显示这个提示信息。举个例子
- explain select min(dept_no),max(dept_no) from dept_emp where dept_no=''; 该语句中,使用到了min()、max()聚合函数,且无任何符合查询条件的记录。
- no matching row in const table (JSON property: message):对于联接查询,连接表中存在有空表或没有表满足唯一索引条件的记录。即,使用了type为const类型的连接查询的数据表中,若不存在符合条件的记录,则执行计划中的Extra列显示这个提示信息。注:前面Impossible WHERE noticed after reading const tables (JSON property: message)解释部分提到过,5.7之后的的const类型的连接查询中若没有匹配值则提示这个信息,除此之外,5.7之后的单表const类型查询或者5.7之前的版本单表const类型或连接表const类型查询没有匹配记录返回的,则提示Impossible WHERE noticed after reading const tables (JSON property: message)信息。举个例子:
- explain select * from dept_emp de,(select emp_no from employees where emp_no=0) tb1 where tb1.emp_no=de.emp_no and de.dept_no='d005'; 注意:该语句在5.6.x版本以及mariadb10.0.x版本的执行计划中会出现两行记录,id=2的行为派生表,派生表的Extra列会出现该提示信息,但在5.7.x以及之后的版本的执行计划中只有一行记录,且Extra列输出为“no matching row in const table”提示,在mariadb 10.1.x及其之后的版本的执行计划中也只有一行记录,但是EXtra输出列为“Impossible WHERE noticed after reading const tables”提示,原因是两者查询优化器内部改写SQL时产生的employees 表的条件值不一样了。
- No matching rows after partition pruning (JSON property: message):对于DELETE或UPDATE,在使用分区表的查询中,执行计划经过分区修剪之后(mysql检索分区表时,会从操作目录中删除不需要使用分区健列检索的分区,这称为分区修剪)没有发现任何匹配记录(mysql的查询优化器在制定查询的执行计划时,若不存在符合WHERE条件的分区,则Extra列中出现这个提示信息),它的意义类似于SELECT语句Impossible WHERE 提示。如果出现时,检查查询语句是否编写错误。
- No tables used (JSON property: message):查询没有FROM子句,或有一个FROM DUAL子句。对于INSERT或REPLACE语句,EXPLAIN在没有SELECT部分时显示此值。 例如,在EXPLAIN INSERT INTO t VALUES(10)中的执行计划会显示这个提示信息,因为这个语句相当于是EXPLAIN INSERT INTO t SELECT 10 FROM DUAL。注:前面提到过,5.6之后的版本可以查看DML的执行计划,但是INSERT和REPLACE除外,另外在5.7之前的版本INSERT和REPLACE的执行计划中会显示这个提示信息,但在5.7之后的版本中执行计划中直接显示null
- Not exists (JSON property: message):MySQL对查询执行LEFT JOIN优化,在LEFT JOIN查询中如果左表中的记录在右表中匹配到了一行,那么就不会在右表中匹配更多行。此时一般会使用not in subquery或not exists运算符,这种连接称为反连接(anti-join)。同理:也可以用于left join查询。先查询外表中的数据,然后再在where子句中检查并获取外部表中的关联字段不为null的记录(注意:这里说的外表,内表是相对于外连接查询(left outer join、right outer join)而言,不是子查询类型的连接)。也就是说,反连接方法常常用于普通连接(inner join)不会出现的结果。
- 举个例子:SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.id IS NULL; 假定t2.id列属性被为NOT NULL。在这种情况下,MySQL扫描t1表并使用t1.id列的值查找t2中的行。 如果MySQL在t2中找到了匹配的行,那么因为t2.id列属性定义not null,那么查询优化器知道t2.id永远不可能为NULL,那么就不会继续扫描t2中具有相同id值的其余行。换句话说,对于t1中的每一行,MySQL需要在t2中只进行一次查找,只要匹配到了一行t2.id不为null则跳过t2表的其余行的扫描,而不管t2中实际匹配多少行。转而使用t1表中的下一行记录进行扫描t2表。
- Plan isn't ready yet (JSON property: none):在命名连接中使用EXPLAIN FOR CONNECTION语句,查询优化器未完成对查询语句创建执行计划时,会出现这个提示信息。 如果执行计划输出包含多个行,则它们中的其中一个或全部输出行的Extra列都可能出现这个提示信息,这取决于优化器执行查询语句的执行计划的进度进展到哪里了。
- Range checked for each record (index map: N) (JSON property: message):MySQL没有发现有合适的索引可以使用,但发现一些索引的列值可能包了原表中的查询数据。对于原表中的每个行组合,MySQL会检查是否可以使用range或index_merge访问方法来检索行。 这不是很快,但是比执行没有索引的连接查询快。index map: N从1开始,按照表的SHOW INDEX显示的顺序。 index map 值N是显示候选索引的位掩码值。 例如,值0x19(二进制11001)意味着将考虑使用index map为1,4和5的索引
- Scanned N databases (JSON property: message):这表示在对INFORMATION_SCHEMA库下的表的查询时,服务器执行会扫描多少个目录,N的值可以是0,1或all。
- 注:mysql从5.0开始提供information_schema数据库,information_schema中存放着mysql服务器内部的元数据信(数据表、数据列、索引等schema信息),整个information_schema库下的信息只支持查询。因为该数据库中所查询到的信息并不是真正存放在磁盘上的数据,而是每当使用SQL访问时,就从mysql服务器内部读取并显示出这些元数据来,这样,一次要访问大量的数据表时,就要花费很长的时间。mysql5.1开始大大提升了访问性能,使用户可以快速访问information_schema数据库,通过改善后的查询检索元数据库信息时,查询执行计划的Extra列会显示此提示信息,Scanned N databases中的N表示读取几个数据库中的信息,取值为0,1,all,分别代表如下含义(0:只请求某个特定表的信息,并不需要读取整个库的元数据信息,1:请求特定数据库内的与该数据库相关的所有schema信息,all:读取mysql服务器内所有数据库的所有schema信息。示例:explain select table_name from information_schema.tables where table_schema='employees' and table_name='employees';,执行计划中Extra列回出现包含 Scanned 0 databases的提示信息,因为只读取了一个表的信息,所以只显示N为0。要注意:正常情况下的业务SQL几乎不会查询information_schema库,所以几乎不会在Extra列中出现该提示信息)
- Select tables optimized away (JSON property: message):使用max()和min()函数聚合函数产生的隐式分组(非group by语句分组)的查询中,有两种情况可能会出现该提示:1、优化器最多返回一行记录,2、为了产生该返回行的记录,查询优化器使用索引读取一些可以确定隐式分组需要扫描的行范围结果集进而使用这个结果集就可以进行确认最终max()或min()需要返回的行记录值。注:只有在优化阶段通过读取索引行来直接读取要返回行记录的查询中,才会出现该提示信息,这样表示在查询真正执行期间不需要再读取任何表直接通过索引就可以返回需要的数据了。举个例子:
- create table test(id int,id2 int,key i_id(id),key i_id2(id2),key i_id_id2(id,id2));insert into test(id,id2) values(1,2),(2,1),(2,3),(3,2),(10,9),(11,100);
- 以上建表语句中可以看到在id和id2列上有i_id和i_id2两个独立索引,id和id2两个列有一个组合索引,那么使用类似语句:explain select max(id) from test;explain select min(id) from test; explain select max(id2) from test;explain select min(id2) from test; explain select min(id),min(id2) from test; explain select max(id),min(id2) from test; explain select max(id),max(id2) from test; explain select min(id),max(id2) from test; 等语句可以直接使用id和id2两个列上的独立索引直接返回最大值和最小值,不需要扫描表。而对于加where条件的(例如把前面理出的SQL加上id或者id2或者id,id2的where条件之后),如果查询优化器能够使用索引(注意:前提是要能够使用到索引,不能使用到索引的不行)直接进行评估并确定出有满足条件的记录时,那么前面会出现这个提示。但是如果必须要去扫描表而不能直接通过索引返回数据的查询,那就不会出现这个提示。
- 对于myisam表,因为myisam表有内部行计数器,所以使用count(*)这种聚合函数的查询中,可以通过内部计数器直接返回查询结果而不需要去扫描表也不需要通过索引来判断,执行计划中也会有这个提示信息出现。但innodb表由于没有这种内部计数器,所以需要依赖索引才行。
- Skip_open_table, Open_frm_only, Open_full_table (JSON property: message):与“Scanned N databases (JSON property: message)”提示信息类似,这些值表示查询可以使用INFORMATION_SCHEMA库下的表的查询来直接返回查询结果时,才会出现这个提示信息:
- Skip_open_table:不 需要扫描表。只通过读取INFORMATION_SCHEMA库下的表直接可以返回查询需要的数据
- Open_frm_only:只有表的.frm文件需要打开
- Open_full_table:未能使用到直接查询INFORMATION_SCHEMA库下的表优化的查询,必须打开.frm,.MYD和.MYI文件
- 注意:以上内容涉及到需要打开.frm、.MYI、.MYD的情况仅针对myisam表,对于innodb表不适用
- Start temporary, End temporary (JSON property: message):这表示半连接查询中使用临时表进行去重的优化策略(Duplicate Weedout特性,5.7版本中引入),Duplicate Weedout这也是优化子查询的另外一种优化方法,使用该优化时会先访问in subquery中的子查询,然后与外部表进行连接查询后将得到的结果保存到临时表中,之后再在临时表中进行去重操作(执行连接操作时,先读取的数据表叫做驱动表,驱动表的执行计划中显示:start temporary提示信息,后读取的数据表叫做被驱动表,被驱动表的执行计划中显示:end temporary提示信息)。注:Duplicate Weedout优化过程与连接查询中的in subquery查询+group by去重的过程是一样的
- 注意:可能需要类似这种in (subquery in ())格式的才会出现,示例:explain select * from employees e where e.emp_no in (select de.emp_no from dept_emp de where dept_no in('d001','d003'));
- 另外:在mysql5.6.35和mysql5.7.17最新版本上测试时,发现并没有使用这个Duplicate Weedout特性特性,而是直接使用了MATERIALIZED物化子查询(默认情况下firstmatch和materialation策略开启,会优先使用这两个特性,把firstmatch和materialation策略关闭之后,再使用上述语句就会出现这个提示信息)。
- unique row not found (JSON property: message):对于诸如SELECT ... FROM tbl_name的查询,两个表连接查询,都使用到了两个表的主键或者唯一索引进行关联查询,并且where条件也是唯一索引或者主键列的,但是连接查询的where条件值在其中一个表中有记录而另外一个表中没有时,没有对应主键或唯一索引匹配记录的在执行计划输出Extra列就可能会显示这个信息(谁出现这个提示信息就看这条记录在哪一边以及使用left join还是right join,如果记录在左边的表,则使用left join并且where条件是使用右表不存在的记录,则右表的explain输出行显示这个提示信息,如果是使用inner join,则查询优化器直接判定不可能有结果,出现Impossible WHERE noticed after reading const tables提示信息。反之亦然)。表示某表中没有记录满足UNIQUE索引或PRIMARY KEY的条件。举个例子:
- 建表和造数语句:create table tb_test1(id int unsigned not null primary key,test1 varchar(100));create table tb_test2(id int unsigned not null primary key,test2 varchar(100));insert into tb_test1 values(1,'1'),(2,'2');insert into tb_test2 values(1,'1');
- 查询语句:explain select t1.* from tb_test1 t1 left join tb_test2 t2 on t1.id=t2.id where t1.id=2; explain select t1.id from tb_test1 t1 left join tb_test2 t2 on t1.id=t2.id where t1.id=2;
- Using filesort (JSON property: using_filesort):MySQL必须做一个额外的排序。首先根据连接类型遍历所有行,并存储排序key和WHERE子句匹配的所有行的行指针完成排序。 然后,按照key排序,并按排序之后的顺序检索行。举个例子(last_name列无索引):explain select * from employees order by last_name; 由于last_name列无索引,所以mysql先将所有需要排序的数据行读取到排序缓冲区,在排序缓冲区进行排序,然后将排序后的结果发送给客户端。
- Using index (JSON property: using_index):仅使用索引就可以返回查询数据的查询,不需要进行额外的回表来读取实际数据行时。可以使用此策略。对于有聚簇索引的InnoDB表,如果“EXtra”列中没有显示“Using index”,但是type是index,key是PRIMARY,表示也可以使用主键索引来直接返回查询数据。有这个提示的查询就叫做覆盖索引查询。
- Using index condition (JSON property: using_index_condition):通过访问索引元组并首先测试读取表以确定是否需要进行全表扫描还是使用索引条件过滤后再读取表。 举个例子:explain select * from employees where hire_date between '1986-06-26' and '1986-07-26';
- Using index for group-by (JSON property: using_index_for_group_by):与“Using index”表访问方法类似,Using index for group-by 表示MySQL发现了一个索引,可用于来处理GROUP BY或DISTINCT列,而不需要在处理GROUP BY或DISTINCT列的时候对实际表进行任何额外的磁盘访问。以最有效的方式使用索引,因此对于每个分组,只读取少数索引条目。如果无法使用索引,那么mysql需要先使用分组基准列进行排序,然后将排序结果进行分组,若使用索引(仅限BTREE索引)处理group by,就会按索引已经排序好的顺序依次读取索引列,不需要额外的排序直接进行分组处理即可。即像这样使用索引处理group by的查询,处理group by的速度相当快且非常高效,执行计划的Extra列就会显示Using index for group-by (JSON property: using_index_for_group_by)信息。
- 处理group by时,常见的可以使用到索引进行松散索引扫描处理group by的情况:要使用松散索引扫描来处理group by,有两种情况:第一种情况是,where条件列和group by列是组合索引,且select字段可以使用这个组合索引进行覆盖,那么执行计划中会出现这个提示信息(如:explain select emp_no,min(from_date),max(from_date) from salaries where from_date>='2002-05-01' group by emp_no;explain select emp_no,min(from_date),max(from_date) from salaries where emp_no>=12000 group by emp_no;)。第二种情况是,无where条件,但是group by中的列和select中的列可以完全使用索引(组合索引和单列索引都可以)进行覆盖,那么执行计划中会出现这个提示信息(如:explain select emp_no,min(from_date),max(from_date) from salaries group by emp_no;)。注意:如果执行计划分析where条件能够返回一个较小的范围,那么就算select字段、where字段和group by字段能够被组合索引覆盖,Extra列也可以不会出现Using index for group-by (JSON property: using_index_for_group_by)提示信息(如:explain select emp_no from salaries where emp_no between 10001 and 10099 group by emp_no;)
- 处理group by时,常见的不能使用到索引进行松散索引扫描处理group by的情况:简单的顺序读取索引的方式(紧凑索引扫描)与只读取索引必要的部分的松散索引扫描的方式是不同的。例如:第一种情况:group by子句的查询中含有avg(),sum(),count()等聚合函数时,处理查询时需要读取所有的索引列,此时即便可以使用索引来处理group by子句,也无法只松散地读取所需的记录,处理这种查询时,虽然可以使用索引处理group by,但是也不能叫做松散索引扫描,这种查询的执行计划中不会出现Using index for group-by (JSON property: using_index_for_group_by)提示信息(如:explain select first_name, count() from employees group by first_name;explain select first_name, avg(emp_no) from employees group by first_name;explain select first_name, sum(emp_no) from employees group by first_name;)。第二种情况:group by子句可以使用到索引,但是where条件无法使用索引,那么mysql会先使用索引进行group by,然后读取数据记录处理where子句中的比较,处理这种查询时,虽然可以使用索引处理group by,但是也不能叫做松散索引扫描,这种查询的执行计划中不会出现Using index for group-by (JSON property: using_index_for_group_by)提示信息(如:explain select first_name from employees where birth_date>'1994-01-01' group by first_name;)
- Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access) (JSON property: using_join_buffer):早期连接表查询会将驱动表的连接字段读入连接缓冲区,然后在缓冲区中使用驱动表的连接字段值来逐行执行与被驱动表进行匹配。 (Block Nested Loop)表示使用了块嵌套循环算法。(Batched Key Access)表示使用批量健访问算法, 也就是说,在EXPLAIN输出的驱动表中的Key将被缓冲,使用缓冲区中的Key与被驱动表关联字段进行匹配,然后从被驱动表中批量获取行数据。
- explain select * from dept_emp de,employees e where de.from_date>'2005-01-01' and e.emp_no<10904;
- Using MRR (JSON property: message):表示查询使用到了多范围读优化策略。
- MRR(multi range read)策略是mysql5.6和mariadb5.3引入的优化功能,在没有这个特性之前的多列条件查询中,有时候需要先通过索引范围查找符合where条件的记录,然后再根据需要读取数据文件的其余记录。通过索引查找到的记录如果相当多时,对相关记录对应的数据文件的读取每次都是采用随机访问方式。引入了MRR就是为了解决这个问题,有MRR时,先通过索引读取一定量的符合where条件的记录,然后使用主键值进行排序,然后使用主键顺序从数据文件中读取行数据(如果在优化器层可以把条件拆分成元组对,则还可以在这一层就把不符合条件的给过滤掉,且还可以按照拆分好的元组对批量进行访问),然后再根据需要继续前面的步骤来查询其余数据,通过这种方式可以有效减少随机读取数据文件的次数。
- 在5.6及其以上的版本中,使用到多范围读的查询在执行计划中显示Using MRR,在mariadb中,使用到了多范围读的查询的执行计划中显示Rowid-ordered scan或者Key-ordered scan提示信息。对于MRR对应的就是mariadb中的Rowid-ordered scan(通过语句explain select * from employees where first_name>='A' and first_name<'B';可以看到),mysql的BKA对应的就是mariadb的Key-ordered scan,不过,mariadb还可以使用hash join的方式来进行连接查询(通过语句explain select * from dept_emp de,employees e where e.emp_no=de.emp_no and de.dept_no in('d001','d002');可以看到BKAH join)。
- mariadb的Key-ordered scan不用于无连接的简单select查询,而是用于存储引擎(xtradb,innodb)数据表的主键连接查询。上述explain select * from dept_emp de,employees e where e.emp_no=de.emp_no and de.dept_no in('d001','d002');语句中,两个表的emp_no都是主键列。从执行计划中来看,先执行de表的读取,再执行与e表的连接,de表在使用emp_no字段与e表连接之前,先对符合de.dept_no in ('d001','d002')条件的de表的emp_no列进行排序,然后再读取e数据表。由于e数据表的emp_no列充当了e表的记录地址,所以需要在读取e表之前先对e表的emp_no字段进行排序,然后根据排序字段读取e表的记录,因此,在mariadb的执行计划中,也会看到BKAH join提示信息。
- 注意:mysql中需要修改optimizer_switch变量为optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on',mariadb中除了修改这个变量值为optimizer_switch='mrr=on,mrr_cost_based=off,mrr_sort_keys=on,optimize_join_buffer_size=on'之外,还需要修改join_cache_level=8,如果join_cache_level=4则只能使用到BKAH,无法使用Key-ordered scan
- Using sort_union(...), Using union(...), Using intersect(...) (JSON property: message):这些表示特定的算法,显示如何为index_merge连接类型选择合并索引扫描策略。在执行计划的type列为Index_merge时,mysql可以使用两个以上的索引执行查询,此时为了进一步说明如何进行索引合并来读取查询结果的。会在执行计划的Extra列显示这几个值:
- Using intersect(...):使用and连接各个使用索引的条件列时,该信息表示从各个处理结果集获取交集
- Using union(...):使用or连接各个使用索引的条件列时,该信息表示从各个处理结果中获取并集
- Using sort_union(...):执行的处理与Using union相同,但在使用or连接查询的数据量比较大的range条件列时,才能使用该方式进行处理,Using sort_union(..)要先读取主键,进行排序合并之后,才能读取记录并返回。
- Using sort_intersection(...):在5.6中,sort_intersection(...)只能用于等值比较条件,但从mariadb5.3开始,使用between或in等范围比较运算符的条件中也可以使用sort_intersection,跟sort_union类似,也是需要先排序,但是是取交集。
- 注:Using union()和Using sort_union()都可以在or连接查询中使用,但是Using union()是用于匹配记录数不多的情况,而使用大于,小于等于这类有很多记录的范围条件时,常常使用Using sort_union()。但是,无论有多少记录,若各where条件中使用的比较条件完全相等时,则使用Using union(),否则使用Using sort_union()。另外,在mariadb内部,Using union()与Using sort_union()使用的排序算法不同,前者使用的是单路排序算法,后者使用的是双路排序算法。
- Using temporary (JSON property: using_temporary_table):表示MySQL需要创建一个临时表来保存中间结果(临时表可以在内存中创建,也可以在磁盘上创建,若查询执行计划中显示该提示信息,则表示使用了临时表,该提示信息是无法看出来是使用的内存临时表还是磁盘临时表。但可以通过状态变量Created_tmp_tables和Created_tmp_disk_tables来查看使用了哪种临时表)。 如果查询包含不同列顺序的GROUP BY和ORDER BY子句,则通常会发生这种情况。举个例子:
- explain select * from employees group by gender order by min(emp_no) ; 该查询中由于group by与order by使用到了不同的数据列,无法同时使用两个索引一个用于排序,一个用于分组。所以需要使用临时 表。其中,group by无法使用索引时在执行计划的Extra列显示Using temporary,order by无法使用索引时,执行计划中的Extra列显示Using filesort(注意:5.7中由于sql_mode默认添加了sql_mode=only_full_group_by,则group by字段必须出现在select列中,否则会无法执行。使用*号也不行)。
- 关于临时表的注意事项(并不是只有执行计划中出现了Using temporary才表示使用到了临时表):1、from子句中的子查询必定使用临时表,也就是常常说的派生表(Derived table),但其实就是临时表,2、含有count(distinct column)的查询如果无法使用索引时,也会创建临时表,3、使用union或unionall的查询总是使用临时表来合并查询结果,4、不能使用索引排序的操作也会使用临时表来做缓冲空间,要排序的记录不断增多时,最后还可能使用到磁盘临时表,排序缓冲本质上就是临时表,使用到排序缓冲进行排序时,执行计划中显示Using filesort。
- Using where (JSON property: attached_condition):WHERE子句用于限制哪些行与下一个表匹配或按照过滤条件过滤之后发送到客户端。举个例子:
- explain select * from employees where emp_no between 10001 and 10100 and gender='F'; 该语句的限制条件(检索条件)为emp_no between 10001 and 10100,gender为检查条件(过滤条件),虽然满足第一个条件emp_no记录显示有100条,但是同时满足两个条件的记录实际上只有37条。而存储引擎会从磁盘读取100条件记录,然后交给mysql server,在server层从100条记录中过滤掉不满足条件的63条记录。而Using where就表示执行了过滤掉符合条件的63条记录的动作。
- 不能使用索引而全表扫描的查询,执行计划中都可能出现Using where。另外,模糊匹配like查询中,通配符不能放在关键字的前面,因为BTREE索引结构原因,只能放到关键字的后边,如:'%abc'、'abc'不能使用索引,只有'abc%'、'abc'才能使用索引。
- Using where with pushed condition (JSON property: message):此项仅适用于NDB表。 表示MySQL集群正在使用条件下推优化来提高非索引列和常量之间直接比较的效率。 在这种情况下,条件被“推下”到集群的数据节点,并在所有数据节点上同时进行评估。 这样就不需要节点之间通过网络发送不匹配的行,使用这种优化策略的查询与不使用条件下推的查询相比可以加速5到10倍。
- Zero limit (JSON property: message):该查询使用了LIMIT 0子句,导致不能选择任何行。在5.7之前的版本中,这种查询语句的Extra列会显示Impossible WHERE,在5.7之后的版本中才显示该提示信息。
上一篇:type列值详解 | 下一篇:explain extended输出格式详解
-
本 WIKI 包含了《千金良方--MySQL 性能优化金字塔法则》一书的代码段加粗命令行命令和SQL语句文本、以及4个附录内容,其中:
- 代码段和高清图单独整理为一个系列文档,如下:
- 每个附录都各自整理成了一个小系列文档,如下:
-
《千金良方--MySQL 性能优化金字塔法则》 一书的作者信息如下:
- 李春、罗小波、董红禹
-
联系人QQ:309969177
-
提示:
-
郑重声明:本WIKI仓库中的资料为电子工业出版社与本书的三位作者共同授权开源,为了在方便大家的同时,避免不必要的纠葛,任何商业与非商业的引用、转载,麻烦大家注明出处,谢谢配合!