您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

准线上事故之MySQL优化器索引选错

时间:2024-03-07 13:55:48  来源:微信公众号  作者:转转技术

1 背景

最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论。排查的过程中,也是学习的过程,下面把排查的过程和分析记录下来,以供大家参考。

2 过程和分析

2.1 问题发现

20年的某个下午,突然收到大量慢查询的告警,同时业务运营在群里反馈红包相关页面加载慢,怀疑系统出问题了,问题发到群里之后,经过日志定位和代码review多重确认,有一条sql成了重点怀疑对象,最终确定的原因是MySQL查询过程中,优化器没有选择最优的索引导致的。

图片图片

需要说明的是,这里使用的MySQL版本是5.7版本。存储引擎是默认的InnoDB

2.2 问题定位

涉及到的表如下:

图片图片

问题sql如下:

select `bonus_id`, `bonus_code`, `over_at`, `status`, `bonus_num`, `product_id` from `zz_test_table` 
where `user_id` = '16723149' and `over_at` > '2020-11-25 20:45:41' and `status` = 0 
and `bonus_id` in ('382364983', '486697270', '486834963') order by `over_at` asc limit 1;

该sql就涉及一张表zz_test_table(真实表名已被隐藏),表里面有两个索引,一个是over_at字段对应的idx_over_at索引,另一个就是bonus_id字段对应的主键索引。

可以看到,sql其实并不复杂,但是执行结果竟然耗费3秒以上,对于一个面向App用户的接口,3秒以上的响应简直无法接受,如果对业务影响严重点的话,甚至于都需要写事故报告了。

果断祭出explAIn大法 先来看看原始的查询情况,如下图:

图片图片

可以看到mysql并没有命中主键索引,而是命中的idx_over_at索引,预估行数为41314647行,这里大家就不要纠结了,为什么这么大的表,历史原因了,后面已经优化掉了。

MySQL官方文档中有描述,我们可以直接强制指定优化器使用我们指定的索引。

图片图片

强制指定使用主键索引试试

图片图片

发现使用强制索引之后,sql执行0.103秒就返回了。

问题定位到这里,好像已经比较清楚了,就是MySQL优化器没有正确选择索引导致的呗。

MySQL:我可不背这个锅,你们自己好好反省下。

MySQL说的有道理,为啥好端端的线上会出现3秒的慢查询呢,这个情况之前为什么没有呢,我们先不管人家MySQL优化器的问题,先来分析下,为什么走了idx_over_at索引之后,3秒都没返回数据呢?

那么idx_over_at索引本身是不是有问题呢?,果然,经过排查,是因为有个小兄弟上线的代码有bug,over_at字段被大量写成同一个值,导致我们原本比较均匀的over_at字段存在了大量重复值,索引检索行数指数上升,已经基本类似全表扫描。

还了MySQL清白之后,我们继续来定位下,为什么优化器不使用更高效率的主键索引呢?在这个过程中,我们又发现一些奇怪的现象。

2.3 问题延伸

奇怪现象一:

图片图片

惊奇的事情发生了,limit 由1 变更为3之后,走了主键索引。

奇怪现象二:

图片图片

惊奇的事情又发生了,order by 把主键ID加上之后,也走了主键索引。

奇怪现象三:

图片图片

惊奇的事情继续发生,套了一层子查询,也走了主键索引。

2.4 问题分析

MySQL:是不是很懵逼,如果碰到此类情况,请问阁下应该如何应对?

得,超出理解范畴了,没办法去翻文档吧。MySql5.7官方文档

图片图片

相对来说,官方的文档关于优化器的说明较为分散,想要快速上手的小伙伴,可以考虑观看阿里云藏经阁出版的深入MySQL实战一书。

附书中关于mysql执行的过程图

图片图片

再来看书中关于优化器的执行过程图

图片图片

从以上资料中,我们得出了一些结论,基于这些结论,最后我们可以思考一些解决办法:

  • 在MySQL里面,优化器的优化依据是执行成本,它的本质是CBO【Cost-based Optimizer,基于成本的优化器】,也就是说执行计划的生成是基于成本的。
  • MySQL优化器工作的前提是了解数据,工作的目的是解析SQL,生成执行计划。但是优化器并没有想象中的那么完善,执行成本主要基于行数去决定,但是扫描行数并不是唯一的执行策略,优化器同时会结合是否使用临时表、是否排序、查询数量等因素进行综合判断。
  • 总的来说,我们上面出现的三种奇怪现象都可以用上面优化器的判断标准去解释,子查询(临时表)、order by(排序) 、limit(查询数量)。

这里我考虑使用优化器的trace工具来详细分析下limit 1 和 limit 3为什么走了不同索引。由于trace会影响性能,我们把部分数据还原到本地进行测试,两次执行sql分别如下:

trace分析LIMIT 3

set optimizer_trace="enabled=on";
select `bonus_id`, `bonus_code`, `over_at`, `status`, `bonus_num`, `product_id` from `zz_test_table` where `user_id` = '16723149' and `over_at` > '2020-11-25 20:45:41' and `status` = 0 and `bonus_id` in ('382364983', '486697270', '486834963') order by `over_at` asc limit 3
select * FROM information_schema.optimizer_trace;
set optimizer_trace="enabled=off";

LIMIT 3 分析结果

图片图片

具体参数解析如下:

  • "range_analysis": {"table_scan": {"rows": 1446041, "cost": 695910 }} 表示全表扫描操作预估会扫描到大约1446041行数据,属于非常大的操作量,全表扫描的预计代价(时间或资源消耗)为695910。
  • "potential_range_indices":  列出了查询优化器分析后认为可以使用的索引。
  • PRIMARY 索引,在本次查询中是可用的。这个索引基于 bonus_id 这一列,idx_over_at 索引,也在本次查询中是可用的。

trace分析LIMIT 1

set optimizer_trace="enabled=on";
select `bonus_id`, `bonus_code`, `over_at`, `status`, `bonus_num`, `product_id` from `zz_test_table` where `user_id` = '16723149' and `over_at` > '2020-11-25 20:45:41' and `status` = 0 and `bonus_id` in ('382364983', '486697270', '486834963') order by `over_at` asc limit 1
select * FROM information_schema.optimizer_trace;
set optimizer_trace="enabled=off";

LIMIT 1 分析结果

图片图片

具体参数解析如下:

  • "rechecking_index_usage": 代表查询优化器对我们的索引进行了重新检查和考虑。
  • {"recheck_reason": "low_limit", "limit": 1, "row_estimate": 3,} :原因(recheck_reason)是因为 LIMIT 参数比较低(只有1),即查询只需要返回一行记录,而先前的索引选择可能返回的记录大于1(estimated 3行)。
  • "range_analysis": {"table_scan": {"rows": 1446041, "cost": 1.74e6 }} 这是查询优化器对主键(通常被视作一种默认索引)进行全表扫描的预估,大约有1446041行数据,预计的成本(用时 or IO次数)是1.74e6。
  • "potential_range_indices": 这列出了查询优化器考虑过的索引和它们可用性。
  • PRIMARY 是第一个索引,也就是主键索引。它在这次查询中并不可用。原因 not_applicable 表示这个索引在查询时并不适用。idx_over_at 是另一个被考虑的索引,结果是可用的。

通过这段日志,我们可以知道查询优化器为了优化查询操作(特别是对 LIMIT 1的优化)做出了一系列的决策和调整,当limit 1的时候,查询优化器认为不使用主键索引的成本会更小。因为这在优化器的成本分析中是更优更快的查询方式。老实说,这里感觉MySQL有点自作聪明了。

3 解决思路

当我们认为SQL的执行计划不合理时,可以使用explain 结合 trace工具去监听整个索引的使用、以及优化器进行优化的一些过程信息,如有必要,可以通过适当的手段去干预优化器。

  • 最快的解决方式应该就是强制指定主键索引了,这种方式在我们需要快速解决线上问题的时候,还是很好用的。但是需要注意的是,强制指定索引是有一定风险的,如果哪天哪个小伙伴在不清楚这里的逻辑之下,修改了索引,极有可能会发生线上事故。
  • 在MySQL的官方文档以及一些其他文章有特别说到,优化器的扫描行数,会随着表的数据新增、删除、字段变更等因素,统计的行数会变的不准确。这里可以考虑使用analyze table table_name 的方式去修复。需要注意的是,这个操作一般小伙伴是没有权限的,涉及线上操作。安全起见,如果需要验证,可以考虑把备份表down到本地去进行验证。
  • 通过order by 、临时表、limit 等去干扰优化器。
  • 设计合理的索引,编写合适的查询语句。MySQL:你这也太泛了

4 总结

这篇文章是基于工作实际中碰到的问题,把问题产生的原因和解决思路总结了下。文中针对提到的一些索引选择差异情况我们结合了解到的优化器执行策略,使用trace工具进行了验证。优化器有一套非常复杂的算法策略,本人对于MySQL的理解深度有限,这里就不详细分析了,还需要继续学习。

另外了解到MySQL 8.0优化器对查询执行计划的选择做了进一步的改进,理想状态下,会基于估算成本选择最有效的执行计划。感兴趣的小伙伴可以去试试。



Tags:MySQL   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  Search: MySQL  点击:(5)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  Search: MySQL  点击:(10)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  Search: MySQL  点击:(8)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  Search: MySQL  点击:(23)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10  Search: MySQL  点击:(5)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  Search: MySQL  点击:(26)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  Search: MySQL  点击:(41)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  Search: MySQL  点击:(50)  评论:(0)  加入收藏
为什么高性能场景选用Postgres SQL 而不是 MySQL
一、 数据库简介 TLDR;1.1 MySQL MySQL声称自己是最流行的开源数据库,它属于最流行的RDBMS (Relational Database Management System,关系数据库管理系统)应用软件之一。LAMP...【详细内容】
2024-02-19  Search: MySQL  点击:(36)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  Search: MySQL  点击:(53)  评论:(0)  加入收藏
▌简易百科推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  爱可生开源社区    Tags:MySQL   点击:(5)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  爱可生开源社区  微信公众号  Tags:MySQL   点击:(10)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  MySQL学习  微信公众号  Tags:MySQL   点击:(8)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  字节跳动技术团队    Tags:ByteHouse   点击:(23)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10    dbaplus社群  Tags:MySQL   点击:(5)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  转转技术  微信公众号  Tags:MySQL   点击:(26)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  数据库干货铺  微信公众号  Tags:MySQL   点击:(41)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  编程技术汇    Tags:MySQL   点击:(50)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  数据库干货铺  微信公众号  Tags:MySQL   点击:(53)  评论:(0)  加入收藏
mysql索引失效的场景
MySQL中索引失效是指数据库查询时无法有效利用索引,这可能导致查询性能显著下降。以下是一些常见的MySQL索引失效的场景:1.使用非前导列进行查询: 假设有一个复合索引 (A, B)。...【详细内容】
2024-01-15  小王爱编程  今日头条  Tags:mysql索引   点击:(82)  评论:(0)  加入收藏
站内最新
站内热门
站内头条