EXPLAIN 使用

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

分析 EXPLAIN 的结果

现在我们来分析一下 EXPLAIN 的结果。

explain select * from salaries where from_date ='1996-12-02';
复制代码

image-20210828110401998

select_type 是 simple 表示这是一个简单查询;查询的表是 salaries;type 是 ALL 表示发生了全表扫描,全秒扫描的性能是最差的;possible_keys、key、key_len 都是空,说明没有使用索引;rows 执行这条 SQL 语句需要扫描两百多万行数据才能返回,filtered 的值是百分之十;最后 extra 的值是 Using where 表示使用了where 条件。

通过执行这条 SQL 语句,可以发现执行这条 SQL 语句需要花费 600 毫秒,说明这条 SQL 语句执行的性能比较差。

下面我们再来分析一条 SQL 语句。

image-20210828111739750

经过运行的结果来抗,从中可以发现 explain 展示了两行结果,当有多行结果的时候,这个 id 字段还是有用的,它可以描述 SQL 语句的执行过程。

如果 explain 的结果包含多个 id 值,比如 id 的值为 1 和 id 的值为 2,那么数字越大越先执行,也就是说 id 的值为 2 的先执行,id 的值为 1 的后执行。而对于相同 id 的行,那么会从上到下依次执行,这一点在前面也介绍过。

其中一条操作了 employees 表,另外一条操作了 salaries 表,由于我们起了别名,所以 table 这一列展示了别名。

从 type 来看,操作 employees 表的时候,是 const,这是一个非常好的级别,使用了主键索引,可能会使用组件,实际使用的也是组件,并且只扫描了一条数据,因为我们指定了条件去查询的。rows x filtered 为 1,也就是 MySQL 预估会使用 employees 表的一行数据和 salaries 的表去关联。

扫描二维码关注公众号,回复: 13378246 查看本文章

同理,操作 表的时候,type 的值是 ref,ref 的性能也是不错的。也使用了主键索引,最后经过估算,需要扫描 17 行数据。

可视化工具分析 SQL

这里我们介绍两款可视化工具,一款是 IntelliJ IDEA,只要这款工具怎么使用,相信很多小伙伴都知道,另一是 MySQL 官网提供一款 MySQL Workbench。下面我们一起来简单使用一下这两款工具。

使用 IntelliJ IDEA 分析 SQL。选中 SQL 语句,右键点击 Explain Plan。

image-20210829104308238

下图所展示的就是通过IntelliJ IDEA 分析 SQL 语句。

image-20210829104501776

此时,可以点击 Show Visualization,展示一个树状的结果。

image-20210829104724428

使用 MySQL 官方提供的 MySQL Workbench 可视化分析。MySQL Workbench 为数据库管理员、程序开发者和系统规划师提供可视化的Sql开发、数据库建模、以及数据库管理功能。

扩展的 EXPLAIN

EXPLAIN 可产生额外的更多的信息,可以通过在 EXPLAIN 语句后紧跟一条 SHOW WARNING 语句查看扩展信息,从而帮助我们了解 SQL 的更多详情。

扩展的 EXPLAIN 与 MySQL 的版本有很大的关系:

在MySQL 8.0.12及更高版本,扩展信息可用于 SELECT、DELETE、INSERT、REPLACE、UPDATE 语句;在 MySQL 8.0.12 之前,扩展信息仅适用于SELECT语句。

在 MySQL 5.6 及更低版本,需使用 EXPLAIN EXTENDED xxx 语句;而从 MySQL5.7 开始,无需添加EXTENDED 关键词。

使用实例:

 explain select * from employees e left join salaries s on e.emp_no = s.emp_no where e.emp_no=10001\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: e
   partitions: NULL
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: s
   partitions: NULL
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 17
     filtered: 100.00
        Extra: NULL
2 rows in set, 1 warning (0.00 sec)
​
mysql> show warnings\g
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                                                                                                                                               |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select '10001' AS `emp_no`,'1953-09-02' AS `birth_date`,'Georgi' AS `first_name`,'Facello' AS `last_name`,'M' AS `gender`,'1986-06-26' AS `hire_date`,`employees`.`s`.`emp_no` AS `emp_no`,`employees`.`s`.`salary` AS `salary`,`employees`.`s`.`from_date` AS `from_date`,`employees`.`s`.`to_date` AS `to_date` from `employees`.`employees` `e` left join `employees`.`salaries` `s` on((`employees`.`s`.`emp_no` = 10001)) where 1 |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
​
复制代码

注意:扩展的 EXPLAIN,使用 IntelliJ IDEA 是无法实现的,必须要在 MySQL 终端里面才可以实现。

通过上面的结果显示来看,MySQL 优化之后的 SQL 语句和原始的 SQL 语句差异还是挺大的。

由于 SHOW WARNING 的结果并不一定是一个有效的 SQL 语句,也不一定能够执行。

在上面实例中没出现特殊标记,在平时的项目中,扩展的 EXPLAIN 在输出中可能会出现以下特殊标记:

  • <auto_key>:自动生成的临时表 key。
  • (expr):表达式(如标量子查询)执行一次,并且将结果值保存在内存中供以后使用。对于由多个值组成的结果,可以创建一个临时表,将会会看到 的信息。
  • (query fragment):将子查询谓词转换为 EXISTS 谓词,然后将子查询转换为可以与 EXISTS 谓词一起使用 。
  • <in_optimizer>(query fragment):这是一个内部优化器对象,对用户没有任何意义。
  • <index_lookup>(query fragment):使用索引查找来处理查询片段,从而查找符合条件的行。
  • (condition, expr1, expr2):如果条件为 true,则取 expr1,否则取 expr2。
  • <is_not_null_test>(expr):验证表达式不为 NULL 的测试。
  • (query fragment):使用子查询实现。
  • materialized-subquery.col_name:在内部物化临时表中对 col_name 引用,以保存评估子查询的结果。
  • <primary_index_lookup>(query fragment):使用主键查找来处理查询片段以查找符合条件的行。
  • <ref_null_helper>(expr):这是一个内部优化器对象,对用户没有任何意义。
  • /* select#N */ select_stmt:将 SELECT 与在非扩展的 EXPLAIN 行相关联,其具有一输出 id 的值 N。
  • outer_tables semi join (inner_tables):半连接操作。 inner_tables 显示未拉出的表。
  • :表示为缓存中间结果而创建的内部临时表。

当某些表属于 const 或 system 类型时,这些表中的列所涉及的表达式将由提前评估涉,并不是所显示语句的一部分。但是,当使用 FORMAT=JSON,某些 const 表访问显示为 ref 使用常量值的访问。

估计查询性能

多数情况下,你可以通过计算磁盘的搜索次数来估算查询性能。对于比较小的表,通常可以在一次磁盘搜索中找到行(因为索引可能已经被缓存了),而对于更大的表,你可以使用 B-tree 索引进行估算︰你需要进行多少次查找才能找到行: log(row_count) / log(index_block_length / 3 * 2 / (index_length + data_pointer_length) ) + 1。

在MysQL中, index_block_length 通常是1024字节,数据指针一般是 4 字节。比方说,有一个 500,000 的表,key 是 3 字节,那么根据计算公式:log(500 ,000 ) /1g ( 1024/3*2/( 3+4) ) + 1 = 4 次搜索。

该索引将需要 500,000 x 7 X 3/2=5.2MB 的存储空间(假设典型的索引缓存的填充率是 2/3),因此你可以在内存中存放更多索引,可能只要一到两个调用就可以找到想要的行了。 但是,对于写操作,你需要四个搜索请求来查找在何处放置新的索引值,然后通常需要 2 次搜索来更新索引并写入行。

前面的讨论并不意味着你的应用性能会因为 logN 而缓慢下降。只要内容被 OS 或 MySQL 服务器缓存,随着表的变大,只会稍微变慢。在数据量变得太大而无法缓存后,将会变慢很多,直到你的应用程序受到磁盘搜索约束(按照 logN 增长)。为了避免这种情况,可以根据数据的增长而增加 key 的。对于MyISAM表,key 的缓存大小由名为 key_buffer_size 的

猜你喜欢

转载自juejin.im/post/7034814415190884389