这个 MySQL bug 99% 的人会踩坑!

面试必考!mysql中 int(10)、char(10)、varchar(10) 的真相,99%的人都答错过 面试被问到“mysql中int(10)、char(10)、varchar(10)有啥区别?”你会不会一愣?别小看这道题,里面全是坑!今天我就用故事+实战,带你彻底搞懂三者区别和使用场景,下次面试不再掉坑。 阅读详情

这周收到一个 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,但足以骗过优化器),优化器认为此时基于全表扫描会更耗性能,于是会选择基于成本大小的方式来选择索引

大规模SoC芯片Hierarchical Flow Calibre LVS教程 大规模SoC芯片Hierarchical Flow Calibre物理验证LVS实现教程 阅读详情

相关推荐

Windows(Win10、Win11)本地部署开源大模型保姆级教程

最近开源大模型可谓闹的沸沸扬扬,尤其是Microsoft开源的和Meta开源的llama3 70b,评测都已经接近了的水平。鉴于还有很多同学不会在上部署和使用本地模型,因此我们出一期保姆级教程。本文讲述了如何在上本地部署大模型。从最基础的软件安装到环境配置都进行了截图展示,希望对非计算机的同学也有所帮助。AI时代我们每个人都必须要掌握一些AI知识,相信不断学习的你一定不会被AI淘汰。这只是开始,后面我们还有很多工作要做,尤其是提示词和智能体。

AIGIS的博客 3万+

故障分析 | 一条 SELECT 语句跑崩了 MySQL ,怎么回事?

作者刘开洋爱可生交付服务团队北京DBA,对数据库及周边技术有浓厚的学习兴趣,喜欢看书,追求技术。本文来源原创投稿*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。在很多疑难问题的排查中,小编最近又遇到了一个select语句执行就会导致MySQL崩溃的问题,特来分享给大家。...

ActionTech的博客 2794

中小型微服务系统 硬件设备如何部署,QPS大概多少

作为一名合格的开发工程师,不仅仅要在前期开发阶段对业务和技术有深入了解,知道选用哪种合适的技术,业务如何拆分,还要思考后期线上环境,资源,延迟,性能等如何优化维护以及分布式系统一些线上生产的一些问题。 本篇主要针对之前了解的情况对硬件设备资源做一个简单的介绍: 首先分布式系统线上实践问题,我们应该对公司的流量,各个服务,网管,注册中心应该如何部署,部署多少台机器才合适,每台机器的配置如何,每天整体流量有多少,高峰期的请求量有多少,你的整体系统是否扛得住(注:很多公司现在直接使用K8S docker 实现

wangxuelei036的博客 1万+

MySQL 一个select把实例搞崩了,原来是表数据异常

全表扫描有问题,那么就是部分数据有异常。几天前,该环境ibdata1的size被置为0,无法启动,使用传输表空间等特殊恢复手段,恢复数据到新实例。多次不同的尝试过后,mysql 错误日志发现新的 row0mysql.cc line 263 重要信息。简单的理解: 从innodb层传递到server层时,一个BLOB类型的大对象断言出错har的影响。单独排除 RESP_DATA 这一列,包含剩下的所有列,访问无问题。定位异常为 text类型的RESP_DATA 字段。相关表的表结构,如下所示。

caicai687800的博客 138

Cadence Allegro PCB文件对比实战:3分钟搞定两份设计差异(附颜色标注技巧)

本文详细介绍了如何高效使用Cadence Allegro的Design Compare功能进行PCB文件差异对比。通过实战工作流,可在3分钟内精准定位并理解两份设计在布局、布线、元件等方面的关键差异,并分享了自定义颜色标注技巧,帮助工程师快速聚焦重要修改,提升设计评审与团队协作效率。

weixin_29248653的博客 380

DCDC--控制方式(Control Mode)

DCDC反馈控制方式

网始如芯个人博客 1万+

捉虫日记 | MySQL 5.7.20 try_acquire_lock_impl 异常导致mysql crash

作者:卢文双 资深数据库内核研发 近期线上MySQL 5.7.20集群不定期(多则三周,短则一两天)出现主库mysql crash、触发主从切换问题,堆栈信息如下;从堆栈信息可以明显看出,在调用 时触发的crash。在官方Bug库未搜到类似问题,转而从代码库入手,搜到对应的BUG —— 8bc828b982f678d6b57c1853bbe78080c8f84e84:

dbkernel 的博客 251

别再用UE5默认材质:用Substance Painter一招提升写实感

今天,我就把这套从模型准备到引擎最终呈现的全流程技巧毫无保留地分享出来,这套方法不仅能让你的作品细节炸裂,更能让你理解次世代PBR(Physically Based Rendering)材质的底层逻辑。

海外旅居设计师,15年设计经验。 1325

MySQL 的这个 BUG,坑了多少人?

点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创 | Java 2020超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析...

weixin_44421461的博客 296

MODTRAN5.2.1安装配置

MODTRAN5.2.1安装配置

Home 4270

Playwright MCP:开启浏览器自动化的新篇章

在当今数字化时代,浏览器自动化技术在众多领域发挥着至关重要的作用,从简单的网页爬取到复杂的自动化测试,再到智能代理与网页的交互等场景,都离不开高效、可靠的浏览器自动化工具。而微软推出的项目,正是在这一领域的一次创新性探索,它为开发者带来了全新的视角和强大的功能支持。本文将深入探讨的概念、架构、代码示例、应用场景、注意事项以及未来发展方向,帮助读者全面了解这一前沿技术。

加入“Super Entity”,与全能开发团队共探AI智能体与数字人项目,开启前沿技术之旅。 5267

MySQL5.7 处理错误1405及碰到的系列错误

MySQL5.7 处理错误1405及碰到的系列错误

李哈哈的博客 1373

【AI智能体】Docker 部署 Coze应用服务实战操作详解

Docker 部署 Coze应用服务实战操作详解

congge 4877

这个 MySQL bug 99% 的人会踩坑!

往期热门文章:1、公司产品太多了,怎么实现一次登录产品互通? 2、用了BigDecimal就不会资损?了解下BigDecimal这五个坑 3、一个依赖搞定 Spring Boot 反爬虫,防止接口盗刷! 4、千万不要把 Request 传递到异步线程里面!有坑! 5、不卷了!入职字节一周就果断跑了。最近收到一个 Sentry 报警,如下 SQL 查询超时了。select ...

Java后端技术 527

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

这个 MySQL Bug 99% 的人会踩坑!

往期热门文章:1、一个Bug能有多大影响:亏损30亿、致6人死亡、甚至差点毁灭世界... 2、推荐几个9月爆火的 GitHub 电商项目 3、18 个终端命令行工具,绝对让你感觉自己像个1...

Java后端技术 359

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【引用】这个 MySQL bug 99% 的人会踩坑!

这个 MySQL bug 99% 的人会踩坑!

wolf909867753的专栏 329

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

这个 MySQL bug,99% 的人会踩坑!

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

m0_71777195的博客 246

mysql 索引的坑

这个 MySQL bug 99% 的人会踩坑!

简单的坚持的专栏 184

面试官问你有什么缺点,99%的人都答错了!

上周有个粉丝找我面试复盘,他面试准备了好几周,各种技术题答得还不错,结果最后被一道送分题直接送走了。

主要分享测试的学习资源,帮助快速了解测试行业,帮助想转行、进阶、小白成长为高级测试工程师。 869

canal踩坑之路

上周公司决定使用canal来做binlog日志的解析 结果这件事就落到了我们架构组。 经过研究决定我们架构组将mysql的binlog日志解析为VO对象发送到kafka上,后端业务通过消费对应kafka的日志来拿到对应表的日志信息。 决定了之后就要开始动手做了 开始踩坑之路 原生架构分析 原生架构在1.1之前是不支持将binlog信息直接解析到kafka上的,而且分析完1.0和1.1的代码结构后...

FengYeDeYanLei的博客 3322
上一篇: Java 8 的 Stream 不好调试?别逗了!IDEA 调试就能轻松搞定!!
下一篇: 超好用的Spring Boot内置工具类,效率翻倍!
程序员职业指南
博客等级 码龄4年 2138粉丝 960原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值