MySQL中常见的慢查询与优化

目录

没有将等值条件列用做组合索引前缀

索引列类型和常量类型不一致

关联字段类型不一致

关联字段字符集不一致

索引列上有表达式计算

[range optimizer限制](#range optimizer限制)

参数作用

参数值

[Range optimizer跳过index dive*](#Range optimizer跳过index dive*)

[Index Dive](#Index Dive)

基于NDV统计信息去估算扫描行数

[MySQL查询优化分析 - 常见慢查问题与优化方法](#MySQL查询优化分析 - 常见慢查问题与优化方法)

explain执行计划详解

没有将等值条件列用做组合索引前缀

如果查询语句的谓词条件是范围条件与等值条件,存在组合索引,那么当范围条件列作为索引前缀的时候,等值条件是无法被range optimizer用来生成引擎扫描range减少扫描行数的。

查看optimize trace中的"potential_range_indexes"可以看到表中的三个联合索引都被标记是可用的,排除了主键索引

在往下看"analyzing_range_alternatives"中的"range_scan_alternatives"可以看到"index": "id_num_name_index"被淘汰,而"index": "num_name_index"和 "index": "name_num_index"被标记可。,但是"index": "num_name_index"扫描"rows": 220,"cost": 77.26,而"index": "name_num_index"扫描"rows": 5,"cost": 2.01

再往下看,可以看到"considered_execution_plans"这里选择了"used_index": "name_num_index"

索引列类型和常量类型不一致

当索引列类型和常量类型不一致的时候,可能导致索引无法使用。例如下面的例子,列类型是varchar,而常量类型是Int。在MySQL中varchar类型和int类型比较是会将varchar转换为int类型去比较,这就导致下面语句需要扫描每一行数据将id转换为int类型再做比较。

关联字段类型不一致

对于JOIN操作,如果关联字段类型不一样,比如一个是Int,一个是varchar,由于比较类型是Int,那么varchar字段的关联索引就无法用来生成REF访问,减少扫描行数。

关联字段字符集不一致

对于JOIN操作,如果关联字段的字符集不一致,这也会导致索引无法被有效利用,进而导致扫描大量数据,查询执行慢。

索引列上有表达式计算

当谓词条件中,索引列上有表达式计算的时候,优化器会无法分析抽取range

range optimizer限制

索引的扫描范围分析是由range optimizer处理,一些场景会触发range optimizer的限制。例如in list或者or的条件数太多,在分析扫描范围区间的时候,会消耗大量内存,导致超过range_optimizer_max_mem_size的大小,这类语句EXPLAIN后,在warnings信息中会有提示:

复制代码
***************************[ 1. row ]***************************
Level   | Warning
Code    | 3170
Message | Memory capacity of 5 bytes for 'range_optimizer_max_mem_size' exceeded. Range optimization was not done for this query.

在 MySQL 中,range_optimizer_max_mem_size 是一个系统变量,用于控制查询优化器在执行查询时可以使用的最大内存大小。这个参数影响查询优化器在处理范围查询(如 BETWEENIN 等)时的行为,特别是当这些查询涉及多个表和大量数据时。

参数作用

  • 内存使用range_optimizer_max_mem_size 指定了查询优化器在处理范围查询时可以使用的最大内存量。如果查询的内存需求超过了这个限制,优化器将尝试使用其他方法来处理查询,例如将范围查询转换为全表扫描。

参数值

  • 默认值:默认值通常为 1MB(1024MB),这意味着查询优化器在处理范围查询时最多可以使用 1MB 的内存。
  • 调整:你可以根据服务器的内存大小和查询的复杂性来调整这个值。增加这个值可以让查询优化器在处理更复杂的范围查询时有更多的内存可用,但也可能增加内存消耗。

Range optimizer跳过index dive*

对于索引范围,range optimizer会通过index dive来估算实际扫描行数。由于这会带来实际的数据访问,有一定的代价开销,优化器会限制index dive的场景。对于in list等值条件特别多的场景,当超过eq_range_index_dive_limit的阈值,优化器就不会做index dive,而是基于NDV统计信息去估算扫描行数。如果in list中有倾斜的数据,那么行数估算就会错误。

Index Dive

总结拿着每个 IN 值或区间边界去索引根页 → 二分定位 → 读取叶子页中的 记录条数(rec_per_key)。

例如:

拿着一个 IN 值 → 找到它在索引里的 边界页假设表 t 有二级索引 idx(c1),且当前 SQL 是

复制代码
SELECT * FROM t WHERE c1 IN (17, 29, 53);

优化器拿到第一个值 17。

从 索引根页(root page) 开始二分:根页里存的是"子页的最小键值+页号"。

例:根页条目 [10→pageA, 20→pageB, 30→pageC]

17 落在 10~20 之间 → 指向 pageA。

继续二分直到 叶子页。pageA 不是叶子,再二分 → 找到 叶子页 L17,叶子页里才是真正按顺序排好序的 (c1, row_id) 记录。在叶子页里 统计 17 这个键值出现了多少条,叶子页内部也是有序的,二分即可找到第一条 17。顺序往后扫,直到键值 ≠ 17,统计行数 → 得到 rec_per_key(17)=5 条。把这个行数累加到本次 range 的总扫描行数。对 29、53 重复 1~3 步,就能算出。

total_rows = rec_per_key(17) + rec_per_key(29) + rec_per_key(53)。

基于NDV统计信息去估算扫描行数

总结:

"基于 NDV 统计信息去估算扫描行数" 就是"用总行数 ÷ 列的不同值个数,再乘以查询涉及的不同值个数"

  1. NDV
    Number of Distinct Values------某一列里 有多少个不同的取值。
    例:列 status 只有 'OK','FAIL','RUNNING' 3 种值 ⇒ NDV = 3。
  2. 统计信息
    MySQL 在执行 ANALYZE TABLE 后,会把
  3. 表总行数 rows
  4. 该列的 NDV存进 mysql.innodb_index_stats (或 mysql.column_stats 里,8.0 直方图更细)。因此 平均每值出现的行数 = rows / NDV。
  5. 估算扫描行数
    当优化器放弃 "index dive" 时,就直接用上面的平均值乘以 查询里要用到的不同值个数。例子:

如果数据倾斜(例如 c1=1 其实有 8 万行,其余值只有 1 行),

估算 100 行 vs 实际 80 004 行,就会出现 "基于 NDV 的估算失真"

  • 表 100 万行,列 c1 NDV = 5 万 ⇒ 平均 20 行/值。
  • SQL:WHERE c1 IN (1,2,3,4,5) (5 个值)。
  • 估算行数 = 20 × 5 = 100 行。

内容整合于 数据库内核月报 - 2024 / 12

MySQL查询优化分析 - 常见慢查问题与优化方法

相关推荐
冉冰学姐2 小时前
SSM学生社团管理系统jcjyw(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·学生社团管理系统·多角色管理
他们叫我技术总监2 小时前
Python 列表、集合、字典核心区别
android·java·python
nvd113 小时前
深入分析:Pytest异步测试中的数据库会话事件循环问题
数据库·pytest
appearappear3 小时前
如何安全批量更新数据库某个字段
数据库
·云扬·4 小时前
MySQL 常见存储引擎详解及面试高频考点
数据库·mysql·面试
羊小猪~~4 小时前
【QT】--文件操作
前端·数据库·c++·后端·qt·qt6.3
coding-fun4 小时前
电子发票批量提取导出合并助手
大数据·数据库
leo_2324 小时前
备份&恢复--SMP(软件制作平台)语言基础知识之三十九
数据库·数据安全·开发工具·smp(软件制作平台)·应用系统
何以不说话4 小时前
mysql 的主从复制
运维·数据库·学习·mysql