前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >第 53 期:EXPLAIN 中最直观的 rows

第 53 期:EXPLAIN 中最直观的 rows

作者头像
爱可生开源社区
发布于 2025-03-21 03:53:27
发布于 2025-03-21 03:53:27
8310
代码可运行
举报
运行总次数:0
代码可运行
作者:杨涛涛,爱可生技术专家。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

MySQL 和大多数关系型数据库一样,SQL 语句执行计划的输出栏都有一行 rows,代表优化器执行这条 SQL 所需算子扫描的记录数,是优化器根据表和索引的统计信息数据评估出来的结果。

如何根据 rows 值的大小判断 SQL 性能?

对于大多数场景来讲,可以直接凭借 rows 值的大小来判断 SQL 语句性能的高低,但也不能一概而论。

本篇就通过几个简单的示例,来列举三种 rows 值判断的情况。

  • rows 值小,性能高
  • rows 值小,性能不一定
  • 不适合看 rows 值

rows 值小,性能高

第一种情况就是同一条 SQL,只是用到索引不同,rows 值越小,SQL 性能越高。

示例 SQL:

select * from t1 where r1=2 and r2=2

如果不考虑真实业务逻辑,单从写法上来讲,这条 SQL 已经无法优化,因为已经足够简单。优化策略可简单的定义为过滤字段是否匹配索引、匹配的索引是否足够好的问题。比如可能有如下四种索引被用到:

idx_r1(r1) / idx_r2(r2) / idx_u1(r1,r2) / idx_u2(r2,r1)

对于以上几个索引,MySQL 可以根据统计信息、数据物理分布、成本模型等选择使用以上四个索引中任意一个,或者直接使用 INDEX MERGE 算法来选择合适的索引组合。

这种情况下,要看哪种索引对这条 SQL 最高效,除了之前介绍过的查看索引本身的数据外,还可以从执行计划的 rows 值直接来判断。

我们使用 force index 来指定优化器强制匹配不同的索引,来看这四个索引对应不同执行计划的 rows 值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
localhost:ytt>desc select * from t1 force index (idx_r1) where r1  = 2 and r2 = 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: idx_r1
          key: idx_r1
      key_len: 5
          ref: const
         rows: 18638
     filtered: 0.10
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

localhost:ytt>desc select * from t1 force index (idx_r2) where r1  = 2 and r2 = 2\G
*************************** 1. row ***************************
...
         rows: 102
     filtered: 11.11
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

localhost:ytt>desc select * from t1 force index (idx_u1) where r1  = 2 and r2 = 2\G
*************************** 1. row ***************************
...
         rows: 12
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

localhost:ytt>desc select * from t1 force index (idx_u2) where r1  = 2 and r2 = 2\G
*************************** 1. row ***************************
...
         rows: 12
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

根据查询计划可知(索引:rows 值):

  • idx_r1: 18638
  • idx_r2: 102
  • idx_u1: 12
  • idx_u2: 12

很明显,在此场景下走联合索引 idx_u1/idx_u2 扫描记录数最小,效率最高。

rows 值小,性能不一定

有些情况下,不能简单通过 rows 值作为判断 SQL 是否高效执行的标准。

示例 SQL:

select * from t1 where r1<5

这条 SQL 也很简单,就是对 r1 进行一个范围过滤完后取结果。依照之前文章里讲的,对于这样的查询,有时候不走索引反而效率更高,虽然单从走索引扫描的 rows 值一定会更小。来看下两条不同的执行计划:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
localhost:ytt>desc select * from t1  where r1  <5 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ALL
possible_keys: idx_r1,idx_u1
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 101745
     filtered: 50.00
        Extra: Using where
1 row in set, 1 warning (0.01 sec)

localhost:ytt>desc select * from t1 force index (idx_r1)  where r1  <5 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: range
possible_keys: idx_r1
          key: idx_r1
      key_len: 5
          ref: NULL
         rows: 50872
     filtered: 100.00
        Extra: Using index condition
1 row in set, 1 warning (0.00 sec)

根据查询计划可知:

  • 执行计划 1 的 rows 值:101745
  • 执行计划 2 的 rows 值:50872

如果仅从 rows 值来判断,那第二个执行计划更优,但事实并非如此。

MySQL 自主选择了第一个执行计划(全表扫描)。其实就是优化器基于一定的数据基础评估,走全表扫的成本要比走索引后再来回表来的更优化。

为了继续验证我们的判断, 查看 EXPLAIN ANALYZE 结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
localhost:ytt>desc analyze select * from t1  where r1  <5 \G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t1.r1 < 5)  (cost=10262.75 rows=50872) (actual time=0.044..104.673 rows=40854 loops=1)
    -> Table scan on t1  (cost=10262.75 rows=101745) (actual time=0.041..90.898 rows=101806 loops=1)

1 row in set (0.12 sec)

localhost:ytt>desc analyze select * from t1 force index (idx_r1)  where r1  <5 \G
*************************** 1. row ***************************
EXPLAIN: -> Index range scan on t1 using idx_r1, with index condition: (t1.r1 < 5)  (cost=22892.66 rows=50872) (actual time=0.437..146.003 rows=40854 loops=1)

1 row in set (0.16 sec)

结果很明显,走全表扫无论成本和最终时间都比走索引有优势。

不适合看 rows 值

前两个情况都是基于单表检索,我们再来看下多表联接的例子。

示例 SQL:

select a.* from t1 a join t2 b using(f0,f1)

这条 SQL 没有过滤条件,仅仅是两表内联,而且表 t1 有 10W 行记录,表 t2 只有 5W 行记录。正常情况,应该走基于主键的 NLJ 算法,表 t2 驱动表 t1

来看下执行计划:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
localhost:ytt>desc select  a.* from t1 a join t2 b using(f0,f1)\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: a
   partitions: NULL
         type: ALL
...
         rows: 101745
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: b
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 8
          ref: ytt.a.f0,ytt.a.f1
         rows: 1
     filtered: 100.00
        Extra: Using index
2 rows in set, 1 warning (0.00 sec)

从执行计划结果来看,表 t1 被放在驱动表的位置,rows 值显示需要扫 10W 行记录(全表扫描);表 t2 随后作为被驱动表来检索(走主键),对于表 t2 的效率很高。

这个结果和我们的认知刚好相反(表 t2 的扫描行数仅仅是针对 NLJ 算法的内表来讲,每次扫描的行数,而不是整体扫描的行数),并且两表 JOIN 的顺序不对,我们强制手动收集统计信息再次进行优化:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
localhost:ytt>analyze table t1,t2;
+--------+---------+----------+----------+
| Table  | Op      | Msg_type | Msg_text |
+--------+---------+----------+----------+
| ytt.t1 | analyze | status   | OK       |
| ytt.t2 | analyze | status   | OK       |
+--------+---------+----------+----------+
2 rows in set (0.43 sec)

收集完后,再次查看执行计划:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
localhost:ytt>desc select  a.* from t1 a join t2 b using(f0,f1)\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: b
   partitions: NULL
         type: index
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 8
          ref: NULL
         rows: 48339
     filtered: 100.00
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: a
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 8
          ref: ytt.b.f0,ytt.b.f1
         rows: 1
     filtered: 100.00
        Extra: NULL
2 rows in set, 1 warning (0.00 sec)

两表执行顺序做了置换,并且总体的 rows 值都变小;表 t2 为驱动表,rows 值接近 5W,表 t1 做为被驱动表进行内部判断。

总结

在不同的情况下,执行计划 rows 值展示出来的信息有不同的参考价值,并不能直接作为 SQL 高效与否的判断标准。

MySQL 的 SQL 到底是走何种执行计划,与执行计划成本模型、表统计信息、索引统计信息、表的数据分布等都有关系,不能仅凭执行计划 rows 值的大小来判断,需要这些因素来综合决定一个最优的执行计划。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-03-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱可生开源社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
可以,大佬,互粉一下
可以,大佬,互粉一下
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
MySQL中explain的结果​字段介绍
昨天说完了执行计划的前四个字段,今天说说后面几个字段吧。我们看看explain的基本语法和输出内容:
AsiaYe
2019/11/06
8.7K0
第 52 期:根据 EXPLAIN EXTRA 栏提示进行优化(四)
Using filesort 是 MySQL 执行计划 EXTRA 栏里最为常见的提示之一,表示 MySQL 要执行这条 SQL 必须要做一个排序的消耗。
爱可生开源社区
2025/03/13
550
第 52 期:根据 EXPLAIN EXTRA 栏提示进行优化(四)
第22期:索引设计(组合索引适用场景)
建立在多个列上的索引即组合索引(联合索引),适用在多个列必须一起使用或者是从左到右方向部分连续列一起使用的业务场景。
爱可生开源社区
2021/03/16
3340
第22期:索引设计(组合索引适用场景)
第 49 期:根据 EXPLAIN EXTRA 栏提示进行优化(一)
经过前面篇幅的持续阅读,相信大家对 MySQL 的执行计划已经有了一个较为深入的理解。本篇将对传统执行计划输出结果里的 Extra 栏进行逐步解析,从而使得大家更进一步的了解 MySQL 的 SQL 优化过程。
爱可生开源社区
2025/01/15
490
第 49 期:根据 EXPLAIN EXTRA 栏提示进行优化(一)
第25期:索引设计(索引的基数与可选择性)
这篇主要介绍 MySQL 索引的 Cardinality 值(基数)以及索引的可选择性。
爱可生开源社区
2021/04/23
6950
第 48 期:EXPLAIN TYPE 列的 JOIN 常见场景详解(下)
对于关系型数据库来说,表的设计 以及 SQL 的写法 尤为重要,占据性能的 90% 都不为过,所以这次专门针对这两大类知识领域,为您抽丝剥茧详细分析。
爱可生开源社区
2025/01/07
760
第 48 期:EXPLAIN TYPE 列的 JOIN 常见场景详解(下)
第 54 期:使用 JSON 格式的执行计划优化 SQL
前面几期我们讲过,EXPLAIN 可以得到一条 SQL 语句的执行计划,获得以下问题的参考数据:
爱可生开源社区
2025/04/10
900
第 54 期:使用 JSON 格式的执行计划优化 SQL
第 51 期:根据 EXPLAIN EXTRA 栏提示进行优化(三)
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。----
爱可生开源社区
2025/02/25
820
第 51 期:根据 EXPLAIN EXTRA 栏提示进行优化(三)
MySQL8索引篇:性能提升了100%!!
今天我们一起来聊聊MySQL 8.x版本中新增的三大索引。MySQL 8.x中新增了三种索引方式,这三种索引方式直接让MySQL原地起飞了,如下所示。
冰河
2022/06/15
2.8K0
第 50 期:根据 EXPLAIN EXTRA 栏提示进行优化(二)
接着上一篇继续,依然是举例说明 EXPLAIN EXTRA 提示栏里的相关释义,这些提示将有助于我们更好的理解 SQL 语句在 MySQL 里的具体执行计划。
爱可生开源社区
2025/01/22
920
第 50 期:根据 EXPLAIN EXTRA 栏提示进行优化(二)
第 47 期:EXPLAIN TYPE 列的 JOIN 常见场景详解(上)
对于关系型数据库来说,表的设计 以及 SQL 的写法 尤为重要,占据性能的 90% 都不为过,所以这次专门针对这两大类知识领域,为您抽丝剥茧详细分析。
爱可生开源社区
2024/12/27
970
第 47 期:EXPLAIN TYPE 列的 JOIN 常见场景详解(上)
一文看懂如何分析MySQL Explain(2/3)
⑨ range:如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法,比如:
程序员小强
2019/06/11
1.6K0
MySQL8.0之降序索引(descending index)
MySQL8.0引入了降序索引(descending index),今天我们来说说这个特性。
AsiaYe
2021/06/09
5.3K0
MySQL8.0之降序索引(descending index)
技术分享 | EXPLAIN 执行计划详解(2)--Extra
爱可生 DBA 团队成员,擅长故障分析、性能优化,个人博客:https://www.jianshu.com/u/a95ec11f67a8,欢迎讨论。
爱可生开源社区
2021/02/26
1.1K0
技术分享 | EXPLAIN 执行计划详解(2)--Extra
第20期:索引设计(前缀索引)
这里主要介绍 MySQL 的前缀索引。从名字上来看,前缀索引就是指索引的前缀,当然这个索引的存储结构不能是 HASH,HASH 不支持前缀索引。
爱可生开源社区
2021/02/01
6700
第19期:索引设计(哈希索引数据分布与使用场景)
哈希索引显式应用主要存在于内存表,也就是 Memory 引擎,或者是 MySQL 8.0 的 Temptable 引擎。本篇的内容上都是基于内存表,MySQL 内存表的大小由参数 max_heap_table_size 来控制,其中包含了表数据,索引数据等。
爱可生开源社区
2021/01/13
3700
索引三剑客之降序索引和不可见索引
MySQL 8.0中引入了三个索引方面的新特性,暂且将其称为“索引三剑客”。前面我们已经学习了三剑客之一的函数索引,现在我们来见识一下另外两剑客 - 降序索引和不可见索引。
吹水老王
2022/05/17
1.5K0
索引三剑客之降序索引和不可见索引
第26期:索引设计(索引下推)
索引下推(INDEX CONDITION PUSHDOWN,简称 ICP)是 MySQL 5.6 发布后针对扫描二级索引的一项优化改进。总的来说是通过把索引过滤条件下推到存储引擎,来减少 MySQL 存储引擎访问基表的次数以及 MySQL 服务层访问存储引擎的次数。ICP 适用于 MYISAM 和 INNODB,本篇的内容只基于 INNODB。
爱可生开源社区
2021/04/23
6260
第23期:索引设计(组合索引不适用场景改造)
上篇文章已经详细介绍 MySQL 组合索引的概念以及其适用场景,这篇主要介绍 MySQL 组合索引的不适用场景以及改造方案。
爱可生开源社区
2021/03/16
2720
第23期:索引设计(组合索引不适用场景改造)
MySQL通过索引优化-这里可能有你不知道的索引优化细节(二)
用执行计划分别测试一下union all、in和or,发现union all分两步执行,而in和or只用了一步,效率高一点。
行百里er
2020/12/02
9060
MySQL通过索引优化-这里可能有你不知道的索引优化细节(二)
推荐阅读
相关推荐
MySQL中explain的结果​字段介绍
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验