0


【MySQL】explain analyze简介

一、阅读及执行顺序

  1. 从右到左:没有遇到并列的迭代器之前,都是从右边开始执行;
  2. 从上到下:遇到并列的迭代器,都是上边的先开始执行。
  • 总结:自上而下,深度优先

二、基础关键词解释

2.1 表结构及sql

CREATE TABLE `demo1` (
  `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键ID',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '姓 
 名',
  `sex` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '性别 {男:1,女:2}',
  `age` int DEFAULT NULL COMMENT '年龄',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '邮箱',
  `content` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '个人简介',
  `create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC ;
EXPLAIN ANALYZE
SELECT *
FROM demo1

2.2 输出

-> Table scan on demo1  (cost=0.35 rows=1) (actual time=0.007..0.007 rows=0 loops=1)

第一部分,操作类型:Table scan on demo1,当前步骤为对表demo1的扫描

第二部分,预估成本:(cost=0.35 rows=1),当前步骤预估共消耗0.35毫秒,读取一行

第三部分,实际成本:(actual time=0.007..0.007 rows=0 loops=1),当前步骤实际读取第一行平均消耗0.007毫秒,读取所有行平均消耗0.007毫秒,读取0行,循环1次(即执行一次)

总结:explain analyze命令输出每一行将由【操作类型+预估成本+实际成本】三部分组成

三、部分场景示例

3.1 级联

3.1.1 表结构及sql

tips:表都没有数据

CREATE TABLE `demo1` (
  `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键ID',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '姓名',
  `sex` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '性别 {男:1,女:2}',
  `age` int DEFAULT NULL COMMENT '年龄',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '邮箱',
  `content` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '个人简介',
  `create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
CREATE TABLE `demo2` (
  `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键ID',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '姓名',
  `sex` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '性别 {男:1,女:2}',
  `age` int DEFAULT NULL COMMENT '年龄',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '邮箱',
  `content` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '个人简介',
  `create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
EXPLAIN ANALYZE
SELECT *
FROM demo1 as d1
    join demo2 as d2
        on d2.id = d1.id

3.1.2 输出

-> Nested loop inner join  (cost=0.70 rows=1) (actual time=0.085..0.085 rows=0 loops=1)
    -> Table scan on d1  (cost=0.35 rows=1) (actual time=0.084..0.084 rows=0 loops=1)
    -> Single-row index lookup on d2 using PRIMARY (id=d1.id)  (cost=0.35 rows=1) (never executed)

执行顺序,遵从【自上而下,深度优先】的原则:

第一步【表扫描】:Table scan on d1 (cost=0.35 rows=1) (actual time=0.084..0.084 rows=0 loops=1)

翻译:扫描d1全表,预计消耗0.35ms并返回1行,实际读取第一行平均花费0.084ms,返回所有行平均花费0.084ms,共循环调用该迭代器1次,返回0行

思考:因为没有数据,但是作为驱动表,所以也需要执行一次该迭代器内容,同时读取第一行和所有行消耗的时间一样

第二步【索引扫描】:Single-row index lookup on d2 using PRIMARY (id=d1.id) (cost=0.35 rows=1) (never executed)

翻译:通过主键索引(聚簇索引)扫描表,仅进行单行扫描,预计消耗0.35ms并返回1行,实际读取第一行平均花费0.084ms,返回所有行平均花费0.084ms,共循环调用该迭代器1次

思考:因为没有数据,不需要执行当前迭代

第三步【嵌套连接】:Nested loop inner join (cost=0.70 rows=1) (actual time=0.085..0.085 rows=0 loops=1)

翻译:进行嵌套表连接,预计消耗0.70ms并返回1行,实际读取第一行平均花费0.085ms,返回所有行平均花费0.085ms,共循环调用该迭代器1次,返回0行

思考:此处的最终执行时间为1*.0.085(即loops*0.085,此处应取单次循环时平均所有航读取时间——后一个0.085)=0.085ms,该执行时间为调用子节点所有迭代器总和,再加上本身执行的时间,所以当前步骤实际执行时间应为(actual time=0.001..0.001)

3.1.3 扩展

因数据会影响最终的优化器结果,所以需要进行数据测试

3.1.3.1 相同数据

demo1和demo2表中写入一模一样的两条数据,执行当前示例相同sql,执行计划如下

-> Inner hash join (d2.id = d1.id)  (cost=1.11 rows=2) (actual time=0.172..0.176 rows=2 loops=1)
    -> Table scan on d2  (cost=0.18 rows=2) (actual time=0.026..0.029 rows=2 loops=1)
    -> Hash
        -> Table scan on d1  (cost=0.45 rows=2) (actual time=0.092..0.105 rows=2 loops=1)

执行顺序,遵从【自上而下,深度优先】的原则:

第一步【表扫描】:Table scan on d2 (cost=0.18 rows=2) (actual time=0.026..0.029 rows=2 loops=1)

第二步【表扫描】:Table scan on d1 (cost=0.45 rows=2) (actual time=0.092..0.105 rows=2 loops=1)

第三步【哈希连接】:Inner hash join (d2.id = d1.id) (cost=1.11 rows=2) (actual time=0.172..0.176 rows=2 loops=1)

思考:由于表中多了数据,优化器在指定执行计划时,与无数据时有了不同的选择,本次选择了哈希连接,当然数据量不多,实际业务过程中的计划的评估与生成会更复杂

3.1.3.2 级联表数据一张表多,一张表少

demo1表写入两条,demo2表中除了与demo1中相同的两条外,在插入id不同的两条数据,执行当前示例相同sql,执行计划如下

-> Nested loop inner join  (cost=1.15 rows=2) (actual time=0.060..0.066 rows=2 loops=1)
    -> Table scan on d1  (cost=0.45 rows=2) (actual time=0.039..0.042 rows=2 loops=1)
    -> Single-row index lookup on d2 using PRIMARY (id=d1.id)  (cost=0.30 rows=1) (actual time=0.010..0.010 rows=1 loops=2)

执行顺序,遵从【自上而下,深度优先】的原则:

第一步【表扫描】:Table scan on d1 (cost=0.45 rows=2) (actual time=0.039..0.042 rows=2 loops=1)

第二步【聚簇索引遍历查找】:Single-row index lookup on d2 using PRIMARY (id=d1.id) (cost=0.30 rows=1) (actual time=0.010..0.010 rows=1 loops=2)

第三步【嵌套循环连接】:Nested loop inner join (cost=1.15 rows=2) (actual time=0.060..0.066 rows=2 loops=1)

思考:

1、此处有一个细节,执行计划将数据量少的d1表当做了驱动表,符合【选择数据量少的表做驱动表】原则,而且【数据量少】这个判断依据并不是数据表中的实际数据量少,是经过where、on条件过滤后提取的数据量少的表。

2、另外,第二步的loops=2,理解为当前迭代器需要执行两次,因为驱动表中有2条符合条件的数据,被驱动表就需要读取两次,相应的第二步的时间应为(actual time=0.010..0.010)×2=(actual time=0.020..0.020)

.此处不再讨论大数据量下的计划理解,有此意者可私聊博主,互相交流与探讨

参考文章:

[译]MySQL EXPLAIN ANALYZE - 墨天轮 (modb.pro)

MySQL8.0:explain analyze分析SQL执行过程 - 简书 (jianshu.com)

MSQL系列(十) Mysql实战-Join驱动表和被驱动表如何区分_mysql驱动表和被驱动表-CSDN博客

标签: mysql 数据库

本文转载自: https://blog.csdn.net/qq_38396859/article/details/135357004
版权归原作者 太上天 所有, 如有侵权,请联系我们删除。

“【MySQL】explain analyze简介”的评论:

还没有评论