这周收到一个 sentry 报警,如下 SQL 查询超时了。

select * from order_info where uid = 5837661 order by id asc limit 1

执行show create table order_info 发现这个表其实是有加索引的

CREATE TABLE `order_info` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`uid` int(11) unsigned,`order_status` tinyint(3) DEFAULT NULL,... 省略其它字段和索引PRIMARY KEY (`id`),KEY `idx_uid_stat` (`uid`,`order_status`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8

理论上执行上述 SQL 会命中 idx_uid_stat 这个索引,但实际执行 explain 查看

explain select * from order_info where uid = 5837661 order by id asc limit 1

可以看到它的 possible_keys(此 SQL 可能涉及到的索引) 是 idx_uid_stat,但实际上(key)用的却是全表扫描

我们知道 MySQL 是基于成本来选择是基于全表扫描还是选择某个索引来执行最终的执行计划的,所以看起来是全表扫描的成本小于基于 idx_uid_stat 索引执行的成本,不过我的第一感觉很奇怪,这条 SQL 虽然是回表,但它的 limit 是 1,也就是说只选择了满足 uid = 5837661 中的其中一条语句,就算回表也只回一条记录,这种成本几乎可以忽略不计,优化器怎么会选择全表扫描呢。

为了查看 MySQL 优化器为啥选择了全表扫描,我打开了 optimizer_trace 来一探究竟

画外音:在MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程

使用 optimizer_trace 的具体过程如下

SET optimizer_trace="enabled=on";        // 打开 optimizer_trace
SELECT * FROM order_info where uid = 5837661 order by id asc limit 1
SELECT * FROM information_schema.OPTIMIZER_TRACE;    // 查看执行计划表
SET optimizer_trace="enabled=off"; // 关闭 optimizer_trace

MySQL 优化器首先会计算出全表扫描的成本,然后选出该 SQL 可能涉及到的所有索引并且计算索引的成本,然后选出所有成本最小的那个来执行,来看下 optimizer trace 给出的关键信息

{"rows_estimation": [{"table": "`rebate_order_info`","range_analysis": {"table_scan": {"rows": 21155996,"cost": 4.45e6    // 全表扫描成本}},..."analyzing_range_alternatives": {"range_scan_alternatives": [{"index": "idx_uid_stat","ranges": ["5837661 <= uid <= 5837661"],"index_dives_for_eq_ranges": true,"rowid_ordered": false,"using_mrr": false,"index_only": false,"rows": 255918,"cost": 307103,            // 使用idx_uid_stat索引的成本"chosen": true}],"chosen_range_access_summary": {    // 经过上面的各个成本比较后选择的最终结果"range_access_plan": {"type": "range_scan","index": "idx_uid_stat",  // 可以看到最终选择了idx_uid_stat这个索引来执行"rows": 255918,"ranges": ["58376617 <= uid <= 58376617"]},"rows_for_plan": 255918,"cost_for_plan": 307103,"chosen": true}}  ...

可以看到全表扫描的成本是 4.45e6,而选择索引 idx_uid_stat 的成本是 307103,远小于全表扫描的成本,而且从最终的选择结果(chosen_range_access_summary)来看,确实也是选择了 idx_uid_stat 这个索引,但为啥从 explain 看到的选择是执行 PRIMARY 也就是全表扫描呢,难道这个执行计划有误?

仔细再看了一下这个执行计划,果然发现了猫腻,执行计划中有一个 reconsidering_access_paths_for_index_ordering 选择引起了我的注意

{"reconsidering_access_paths_for_index_ordering": {"clause": "ORDER BY","index_order_summary": {"table": "`rebate_order_info`","index_provides_order": true,"order_direction": "asc","index": "PRIMARY",    // 可以看到选择了主键索引"plan_changed": true,"access_type": "index_scan"}}
}

这个选择表示由于排序的原因再进行了一次索引选择优化,由于我们的 SQL 使用了 id 排序(order by id asc limit 1),优化器最终选择了 PRIMARY 也就是全表扫描来执行,也就是说这个选择会无视之前的基于索引成本的选择,为什么会有这样的一个选项呢,主要原因如下:

The short explanation is that the optimizer thinks — or should I say hopes — that scanning the whole table (which is already sorted by the id field) will find the limited rows quick enough, and that this will avoid a sort operation. So by trying to avoid a sort, the optimizer ends-up losing time scanning the table.

从这段解释可以看出主要原因是由于我们使用了 order by id asc 这种基于 id 的排序写法,优化器认为排序是个昂贵的操作,所以为了避免排序,并且它认为 limit n 的 n 如果很小的话即使使用全表扫描也能很快执行完,所以它选择了全表扫描,也就避免了 id 的排序(全表扫描其实就是基于 id 主键的聚簇索引的扫描,本身就是基于 id 排好序的)

如果这个选择是对的那也罢了,然而实际上这个优化却是有 bug 的!实际选择 idx_uid_stat 执行会快得多(只要 28 ms)!网上有不少人反馈这个问题,而且出现这个问题基本只与 SQL 中出现 order by id asc limit n这种写法有关,如果 n 比较小很大概率会走全表扫描,如果 n 比较大则会选择正确的索引。

这个 bug 最早追溯到 2014 年,不少人都呼吁官方及时修正这个bug,可能是实现比较困难,直到 MySQL 5.7,8.0 都还没解决,所以在官方修复前我们要尽量避免这种写法,如果一定要用这种写法,怎么办呢,主要有两种方案

  1. 使用 force index 来强制使用指定的索引,如下:

select * from order_info force index(idx_uid_stat) where uid = 5837661 order by id asc limit 1

这种写法虽然可以,但不够优雅,如果这个索引被废弃了咋办?于是有了第二种比较优雅的方案

  1. 使用 order by (id+0) 方案,如下

select * from order_info where uid = 5837661 order by (id+0) asc limit 1

这种方案也可以让优化器选择正确的索引,更推荐!为什么这个 trick 可以呢,因为此 SQL 虽然是按 id 排序的,但在 id 上作了加法这样耗时的操作(虽然只是加个无用的 0,但足以骗过优化器),优化器认为此时基于全表扫描会更耗性能,于是会选择基于成本大小的方式来选择索引。

mysql sql查询超时排查相关推荐

  1. sql 查询超时已过期_监视来自SQL Server代理作业的查询超时过期消息

    sql 查询超时已过期 SQL Server provides you with a good solution to automate a lot of your administrative ta ...

  2. mysql 超时_为MySQL设置查询超时

    昨天有人在群里问, MySQL是否可以设置读写超时(非连接超时), 如果可以就可以避免一条SQL执行过慢, 导致PHP超时错误. 这个, 其实可以有. 只不过稍微要麻烦点. 首先, 在libmysql ...

  3. oracle大数据量查询超时排查

    首先声明,为实际项目中用到技术,绝非水文,手打不易,禁止抄袭!!!!!! 项目背景,做的是银行项目,ods实时查询接口,java开发接口,数据库为Oracle 19c.最近生产运维反馈,手机银行查询个 ...

  4. Mysql数据库查询超时,这样优化快速解决问题

    ◆ 问题发现 期初在七月份时,经常发现有几个定时任务报错,查看了下异常原因,大概定位是数据库执行异常 ◆ 查找原因 1 和 DBA 排查 mycat(公司使用 mycat ) 和 mysql 的错误日 ...

  5. 【MySQL】MySQL SQL查询语法建议

    1.概述 主要是一些MySQL操作的优化,以及建议 2.建表语句 数据库要满足基本范式: 选择合适的数据类型:尽量定长: 不要使用无法加索引的类型作为关键字段,比如text类型: 为了避免联表查询,有 ...

  6. mysql sql查询昨天的数据_sql语句,查询昨天的数据

    如果在程序中,有前台传来两个时间点:beginTime和endTime,在sql查询中的限制条件就是查询昨天的数据,那么可以这样写: 但是如果在这里要查询昨天的数据的话, 则不能简单地在开始时间的那里 ...

  7. mysql left join超时,MySQL 行锁超时排查方法优化

    一.大纲 #### 20191219 10:10:10,234 | com.alibaba.druid.filter.logging.Log4jFilter.statementLogError(Log ...

  8. mysql 行锁 超时_技术分享 | MySQL 行锁超时排查方法优化

    作者:xuty 本文来源:原创投稿 * 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源. 一.大纲 #### 20191219 10:10:10,234 | com.ali ...

  9. mysql 慢查询原因 排查 优化

    慢查询原因 一条sql偶尔查询很慢 原因 数据库更新频繁,redo log buf很快满了,只能先暂停其他操作,将redo log buf同步写到磁盘上时,这时候查询只能等待写磁盘完成了. 查询涉及到 ...

最新文章

  1. C语言程序设计 细节总结(第8章 指针)
  2. oracle所有表相关查询
  3. 自学python需要多长时间-自学Python要学多久可以学会?
  4. python3网上学习资源汇总
  5. Django中ORM之或语句查询
  6. java正则表达式性能_译:Java 中的正则表达式性能概述
  7. mac下安装前端模板引擎Jinja2
  8. php socket keepalive,linux keepalive探测对应用层socket api的影响
  9. python建立sqlite数据库_5分钟快速入门,用Python做SQLite数据库开发,附代码适合初学...
  10. 3d激光雷达开发(平面分割)
  11. 视频编解码(二):编解码器基础知识
  12. Guitar Rig 6 for mac(电吉他软件效果器)
  13. 第九篇 设计模式之装饰模式
  14. [读论文]三维激光扫描点云数据处理研究进展、挑战与趋势(2017)
  15. 2016版excel_【重磅分享】最完整EXCEL教程,视频+PPT下载
  16. 转:机器人工程师学习计划(YY硕)(后悔自己没有早点看到强力推荐)
  17. 服务器系统万能驱动,IT天空万能驱动程序
  18. 读书笔记-人月神话13
  19. 美国大学计算机专业排名 圣地亚哥,U.S.News美国大学计算机专业排名
  20. android 新浪微博分享链接地址,Android 集成新浪微博分享及授权 (上)

热门文章

  1. 计算机cpu、寄存器、内存区别
  2. WhatsUp v14.4 新特性
  3. Python爬取游戏英雄皮肤图片 王者+LOL
  4. 怎样在一个排序函数中同时实现升序或者降序
  5. 40页PPT|中小学智慧校园顶层设计规划方案(附PPT下载)
  6. STM32+OLED屏显应用实例
  7. 如何在免费追剧?Python制作视频解析免费追剧神器
  8. 烘焙贴图(二)——展UV
  9. 金融历史数据导入之股票 level2 逐笔篇
  10. 【机器学习】主题建模+隐狄利克雷分配模型(LDA)+吉布斯采样