Mysql:记一次线上索引失效

本文讲述了作者在排查服务质量问题时,发现一个接口的SQL查询频繁超时,未走索引。通过分析发现,不走索引主要因为`bus_id>0`和`时间范围`条件可能导致全表扫描。讨论了优化器行为、不走索引的规则,并提供了强制使用索引的解决方案。

一、起因

今天在看服务质量看板时,发现我的一个服务质量明显偏低,可用性不到三个9,根据监控面板找到一个分页查询数据的接口频繁失败,故此找一下原因

二、排查过程

通过查询错误日志,发现该接口select count(*) from table where ...频繁超时,怀疑是没走索引,拿出当时的sql做explain解释,发现确实没走,伪sql如下:

-- 注:bus_id和create_time都单独建了索引
select count(*) 
from table_a
where bus_id > 0    -- 一个业务id,bigint类型(数据库有极少部分是空值)
and create_time >= '2021-05-01 00:00:00'
and create_time < '2021-05-31 23:59:59'

这里为了说明为什么没走索引,先描述一下我对sql进行尝试的几种现象,从现象分析问题:

  1. 原sql进行了全表扫描;
  2. 删除bus_id>0,explain解释走了索引,type为range;
  3. 删除create_time条件,发现没走索引;
  4. 两个条件都保留,create_time 时间范围缩小到一天以内,explain发现走了索引;

三、分析

针对以上:2-4种情况,进行逐步分析

情况2:

此种情况属于正常的求总数sql,不做分析;

情况3:

删除了create_time后,发现bus_id>0没有走索引

首先明确了一个这样的知识点:

  • 如果使用了 not in , not exists , (<> 不等于 !=) 这些不走索引;
  • < 、 > 、 <= 、>= 这个根据实际查询数据来判断,如果全盘扫描速度比索引速度要快则不走索引 ;

因为手头有innoDB存储引擎一书,在5.6.4章节找到了对于第二条的解释:

 

以大于号为例,bus_id>0扫描到的数据太多了,导致优化器自动选择了不走索引。其他<、<=、>=结果一样

情况4:

在知道了情况3不走索引后,试着将create_time时间范围缩小到一天内,结果惊奇的发现竟然走了索引,针对这种情况,询问了公司的DBA

这里其实还是优化器导致的,时间范围过长,优化器认为范围查找和bus_id>0差别不大,结果都不走索引了。

假如时间范围比较短,则会走时间范围索引;

当然优化器内部具体的实现逻辑肯定很复杂,太具体的还需要继续研究;

可以使用force index强制走create_time 索引;

-- 注:bus_id和create_time都单独建了索引
select count(*) 
from table_a
FORCE INDEX(idx_create_time)
where bus_id > 0    -- 一个业务id,bigint类型(数据库有极少部分是空值)
and create_time >= '2021-05-01 00:00:00'
and create_time < '2021-05-31 23:59:59'

 

 

 

 

 

<think>嗯,用户想了解MySQL中隐式类型转换导致索引失效的原因。这个问题在数据库优化中很常见,需要从底层机制讲清楚。 用户提供的引用材料很有价值,特别是引用[1]详细描述了线上事故案例:8000万行表因为隐式转换导致全表扫描超时30秒。引用[3]则解释了B+树索引的有序性被破坏的关键点。 需要重点解释三个核心机制:首先是MySQL的类型转换规则,当字符串与数字比较时,会把字符串转为浮点数;其次是索引的B+树结构依赖数据的严格有序性;最后是转换后的值破坏了这种有序性,比如"1"、" 1"、"1a"都会变成1。 用户可能还关心如何避免这种情况,所以应该给出具体解决方案,比如保持字段类型一致、使用CAST函数显式转换等。另外引用[4]提到的EXPLAIN命令也很实用,可以教用户如何验证。 考虑到用户的技术水平,解释时要避免太理论化。用那个"1"、" 1"、"1a"的例子就很形象,说明为什么索引无法精确定位。最后生成的问题要覆盖进阶内容,比如如何检测和优化这类问题。</think>MySQL中隐式类型转换导致索引失效的核心原因在于**破坏了B+树索引的有序性**。以下是详细分析: ### 一、根本原因:有序性被破坏 1. **B+树索引依赖严格有序性** MySQL索引(如InnoDB)基于B+树结构,要求索引列的值具有严格顺序(例如数字按大小、字符串按字典序)。 *示例*:对字符串列`phone`建立索引,其值在B+树中按`'123'`→`'456'`→`'789'`顺序存储。 2. **隐式转换破坏有序性** 当查询条件与索引列类型不匹配时(如数字`= 123` vs 字符串索引`phone`),MySQL会将索引列**逐行隐式转换**为查询条件的类型(此处转为数字)。 转换后出现以下问题: - 原有序值被转为无序数字(如`'123'`→`123`, `'456'`→`456`) - 不同字符串可能转为相同数字(如`'123'`、`'123 '`、`'0123'`均转为`123`) - 非法值转为`0`(如`'abc'`→`0`) ```sql -- 失效案例:phone是VARCHAR列,但条件用数字 SELECT * FROM users WHERE phone = 123; -- 索引失效 ``` ### 二、为何优化器放弃索引? 1. **转换后无法利用B+树的有序性** 优化器发现: - 需对**所有索引值**进行转换(相当于全索引扫描) - 转换结果无法直接定位到B+树的某个范围 > 此时全表扫描可能比“全索引扫描+回表”代价更低[^1]。 2. **索引值重复风险** 如`phone`列有值`'123'`、`'123 '`(末尾空格),隐式转数字后均为`123`。若使用索引,可能遗漏部分数据,因此优化器选择更安全的全表扫描[^3]。 ### 三、典型案例分析 | 场景 | 是否失效 | 原因分析 | |---------------------|----------|------------------------------| | `WHERE int_col = '1'` | 否 | 字符串转数字(值唯一) | | `WHERE str_col = 1` | **是** | 索引列逐行转换(破坏有序性) | | `WHERE str_col = '1'` | 否 | 类型匹配,直接使用索引 | | `WHERE str_col = CAST(1 AS CHAR)` | 否 | 显式转换保证类型一致 | ### 四、解决方案 1. **保持数据类型一致** ```sql -- 正确:phone为VARCHAR则用字符串查询 SELECT * FROM users WHERE phone = '123'; ``` 2. **显式转换查询条件** ```sql SELECT * FROM logs WHERE event_time = CAST('2023-01-01' AS DATETIME); ``` 3. **使用`EXPLAIN`验证索引** ```sql EXPLAIN SELECT * FROM orders WHERE order_date = 20230101; -- 检查type列是否为ALL(全表扫描) ``` 4. **修改表结构对齐类型** 如将`ref_id VARCHAR(10)`改为`ref_id INT`,确保与关联表类型一致[^1]。 > **关键结论**:隐式转换本质是**破坏了索引的有序性**,使B+树无法快速定位数据。避免此问题的核心在于**确保查询条件与索引列的存储类型严格一致**[^1][^3]。 --- ### 相关问题 1. 如何通过`EXPLAIN`结果判断MySQL是否使用了索引? 2. 除了隐式转换,还有哪些常见场景会导致MySQL索引失效? 3. 当发现索引失效时,有哪些系统表(如`information_schema`)可以帮助分析原因? 4. 字符串类型的索引在什么情况下可能比数值型索引性能更差? [^1]: MySQL隐式转换规则导致索引失效的机制分析 [^2]: 索引失效场景与MySQL版本的关联性 [^3]: B+树索引有序性与隐式转换的冲突原理 [^4]: 使用EXPLAIN验证索引使用情况的实践方法
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值