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

第 53 期:EXPLAIN 中最直观的 rows

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

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

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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Android 内存优化】Android 原生 API 图片压缩原理 ( 哈夫曼编码开关 | 哈夫曼编码原理 | libjpeg-turbo 函数库 )
【Android 内存优化】图片文件压缩 ( Android 原生 API 提供的图片压缩功能能 | 图片质量压缩 | 图片尺寸压缩 ) 简要介绍了 图片文件压缩格式 , 以及 Android 提供的图片质量 , 尺寸压缩原生 API ;
韩曙亮
2023/03/27
8090
【Android 内存优化】Android 原生 API 图片压缩原理 ( 哈夫曼编码开关 | 哈夫曼编码原理 | libjpeg-turbo 函数库 )
图像库 libjpeg-turbo 编译与实践
在 libjpeg-turbo 的源码中就已经有了讲述如何编译的 BUILDING.md 文件,还是使用 CMake 进行编译,大体方法和参数设置都大同小异了。
音视频开发进阶
2019/07/24
3.4K0
【Android FFMPEG 开发】FFMPEG 交叉编译配置 ( 下载 | 配置脚本 | 输出路径 | 函数库配置 | 程序配置 | 组件配置 | 编码解码配置 | 交叉编译配置 | 最终脚本 )
① FFMPEG 源码下载地址 : http://ffmpeg.org/download.html#releases
韩曙亮
2023/03/27
1.7K0
【Android FFMPEG 开发】FFMPEG 交叉编译配置 ( 下载 | 配置脚本 | 输出路径 | 函数库配置 | 程序配置 | 组件配置 | 编码解码配置 | 交叉编译配置 | 最终脚本 )
【Android 内存优化】libjpeg-turbo 函数库交叉编译与使用 ( 交叉编译脚本编写 | 函数库头文件拷贝 | 构建脚本配置 | Android Studio 测试函数库 )
交叉编译脚本参考 : 之前已经做过两个函数库的交叉编译脚本 , FFMPEG 和 x264 开源库 , 而且都是使用 configure 生成 Makefile 文件 ;
韩曙亮
2023/03/27
1.8K0
【Android 内存优化】libjpeg-turbo 函数库交叉编译与使用 ( 交叉编译脚本编写 | 函数库头文件拷贝 | 构建脚本配置 | Android Studio 测试函数库 )
【Android RTMP】Android Studio 集成 x264 开源库 ( Ubuntu 交叉编译 | Android Studio 导入函数库 )
① H.264 标准 : H.264 是视频编码标准 , 由 ITU 和 MPEG 制订 ;
韩曙亮
2023/03/27
2.7K0
【Android RTMP】Android Studio 集成 x264 开源库 ( Ubuntu 交叉编译 | Android Studio 导入函数库 )
Assimp Android 编译
Assimp的全称是Open Asset Import Library,一个很流行的OpenGL 3D+4D 模型处理框架。提供C/C++的API,提供C#, Java, Python, Delphi, D等语言的封装调用。支持Android和iOS平台。本文详细介绍如何编译适用于Android平台的.so库,并记录过程中踩到的坑。 准备知识 将一个C++的工程编译成Android平台可用的.so库,需要用到一些额外的工具。在开始之前,最好先了解下相关的知识点。 make与makefile 我们在处理
wxdut.com
2018/06/19
2.6K0
【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( 初始化压缩对象 | 打开文件 | 设置压缩参数 | 写入压缩图像数据 | 完成压缩 | 释放资源 )
上一篇博客 【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 ) 介绍了从 Java 层传入 Bitmap 对象到 JNI 层 , JNI 层获取到了图像对应的 RGB 像素数据 , 本篇博客中将获取的图像数据进行压缩 , 存储到 JPEG 格式图片中 ;
韩曙亮
2023/03/27
2.1K0
nmake(vs2015)命令行编译libjpeg-turbo(x86/amd64)
版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/51732883
10km
2019/05/25
2.3K0
深入理解使用CMake编译 NDK 程序
Android Studio 2.2 及以后的版本默认使用CMake进行 NDK 编译, 其中最吸引人的地方是,在开发NDK程序时可以进行联机调试,这真是大在的方便了开发者开发NDK程序的效率了。 那么使用CMake编译NDK程序是否与我们之前介绍的使用ndk-build编译有很大的不同呢?下面我们就来一窥它的原理。
音视频_李超
2020/04/02
4K0
【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )
在上一篇博客 【Android 内存优化】libjpeg-turbo 函数库交叉编译与使用 ( 交叉编译脚本编写 | 函数库头文件拷贝 | 构建脚本配置 | Android Studio 测试函数库 ) 中 对 libjpeg-turbo 函数库进行了交叉编译 , 拷贝了相应的头文件和静态库到 Android Studio 项目中 , 并配置了 CMakeList.txt 构建脚本 , 和 build.gradle 构建脚本 , 本篇博客中开始进行代码编写 ;
韩曙亮
2023/03/27
1.2K0
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
​《FFmpeg开发实战:从零基础到短视频上线》一书的“12.1.2 交叉编译Android需要的so库”介绍了如何在Windows环境交叉编译Android所需FFmpeg的so库,前文又介绍了如何在Linux环境交叉编译Android所需FFmpeg的so库,接下来介绍如何在Linux环境交叉编译Android所需x265的so库。
aqi00
2024/03/23
9720
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
【FFmpeg】编译不同平台的 FFmpeg 源码 ( 本地编译 与 交叉编译 | FFmpeg 指定目标平台的编译配置参数 | 配置目标 CPU 架构 | 配置目标操作系统 )
" 本地编译 " 指的是 在 目标系统 上进行编译的过程 , 生成的 可执行文件 和 函数库 只能在 目标系统 中使用 ;
韩曙亮
2024/05/24
1.6K0
Android NDK 报错:undefined reference to ‘main‘(invalid character)解决办法
后来盯着 CMakeList,看到这些编译、link 优化项,心想也没有可能是这些的配置导致的:
张拭心 shixinzhang
2020/10/29
3.5K0
Android NDK 报错:undefined reference to ‘main‘(invalid character)解决办法
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )
① 视频数据回顾 : 手机使用 Camera 采集 NV21 格式的图像数据 , x264 编码库将图像数据编码成 H.264 格式的视频数据 ;
韩曙亮
2023/03/27
9120
【Android RTMP】音频数据采集编码  ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )
【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)
博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293
韩曙亮
2023/03/27
5.1K0
【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)
【Android 安装包优化】使用 lib7zr.a 静态库处理压缩文件 ( 交叉编译 lib7zr.a 静态库 | 安卓工程导入静态库 | 配置 CMakeLists.txt 构建脚本 )
在之前的一系列博客中使用 lib7zr.so 动态库处理压缩文件 , 本篇博客中使用静态库处理压缩文件 , 仅做参考 ;
韩曙亮
2023/03/29
3.2K0
【Android 安装包优化】使用 lib7zr.a 静态库处理压缩文件 ( 交叉编译 lib7zr.a 静态库 | 安卓工程导入静态库 | 配置 CMakeLists.txt 构建脚本 )
用cmake交叉编译到iOS和Android
最近看了下最新版本的cmake的文档,很惊喜地发现他已经支持直接设置Android和OSX的一些变量了,然后有瞄了一眼NDK,发现里面现在也停工官方的cmake支持。
owent
2018/08/01
3.8K0
【错误记录】Android NDK 错误排查记录 ( error: undefined reference to | Linking CXX shared library FAILED )
CMakeList.txt 构建脚本如下 : 跟着该构建脚本 , 逐步向下排查 ;
韩曙亮
2023/03/27
2K0
【Android 安全】DEX 加密 ( 代理 Application 开发 | 交叉编译 OpenSSL 开源库 )
OpenSSL 是开源密码库 , 其中封装了常用的 密码算法 , 常用密钥 , 证书封装管理 , SSL 协议 ;
韩曙亮
2023/03/28
8450
【Android 安全】DEX 加密 ( 代理 Application 开发 | 交叉编译 OpenSSL 开源库 )
【Android NDK 开发】NDK 交叉编译 ( NDK 函数库目录 | Linux 交叉编译环境搭建 | 指定头文件目录 | 指定函数库目录 | 编译 Android 命令行可执行文件 )
1 . Android 版本目录 : platforms 中存储了各个 Android 版本编译时需要的动态库与静态库资源 , 如 android-29 中就是该版本对应的本地库资源 ;
韩曙亮
2023/03/27
5.7K0
【Android NDK 开发】NDK 交叉编译 ( NDK 函数库目录 | Linux 交叉编译环境搭建 | 指定头文件目录 | 指定函数库目录 | 编译 Android 命令行可执行文件 )
推荐阅读
【Android 内存优化】Android 原生 API 图片压缩原理 ( 哈夫曼编码开关 | 哈夫曼编码原理 | libjpeg-turbo 函数库 )
8090
图像库 libjpeg-turbo 编译与实践
3.4K0
【Android FFMPEG 开发】FFMPEG 交叉编译配置 ( 下载 | 配置脚本 | 输出路径 | 函数库配置 | 程序配置 | 组件配置 | 编码解码配置 | 交叉编译配置 | 最终脚本 )
1.7K0
【Android 内存优化】libjpeg-turbo 函数库交叉编译与使用 ( 交叉编译脚本编写 | 函数库头文件拷贝 | 构建脚本配置 | Android Studio 测试函数库 )
1.8K0
【Android RTMP】Android Studio 集成 x264 开源库 ( Ubuntu 交叉编译 | Android Studio 导入函数库 )
2.7K0
Assimp Android 编译
2.6K0
【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( 初始化压缩对象 | 打开文件 | 设置压缩参数 | 写入压缩图像数据 | 完成压缩 | 释放资源 )
2.1K0
nmake(vs2015)命令行编译libjpeg-turbo(x86/amd64)
2.3K0
深入理解使用CMake编译 NDK 程序
4K0
【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )
1.2K0
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
9720
【FFmpeg】编译不同平台的 FFmpeg 源码 ( 本地编译 与 交叉编译 | FFmpeg 指定目标平台的编译配置参数 | 配置目标 CPU 架构 | 配置目标操作系统 )
1.6K0
Android NDK 报错:undefined reference to ‘main‘(invalid character)解决办法
3.5K0
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )
9120
【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)
5.1K0
【Android 安装包优化】使用 lib7zr.a 静态库处理压缩文件 ( 交叉编译 lib7zr.a 静态库 | 安卓工程导入静态库 | 配置 CMakeLists.txt 构建脚本 )
3.2K0
用cmake交叉编译到iOS和Android
3.8K0
【错误记录】Android NDK 错误排查记录 ( error: undefined reference to | Linking CXX shared library FAILED )
2K0
【Android 安全】DEX 加密 ( 代理 Application 开发 | 交叉编译 OpenSSL 开源库 )
8450
【Android NDK 开发】NDK 交叉编译 ( NDK 函数库目录 | Linux 交叉编译环境搭建 | 指定头文件目录 | 指定函数库目录 | 编译 Android 命令行可执行文件 )
5.7K0
相关推荐
【Android 内存优化】Android 原生 API 图片压缩原理 ( 哈夫曼编码开关 | 哈夫曼编码原理 | libjpeg-turbo 函数库 )
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验