当前位置: 首页 > news >正文

SQL常见索引失效导致慢查询情况

数据库索引(尤其是B+树结构的索引)的作用类似于字典的目录,它需要是有序的。任何破坏这种有序性的操作,都可能使数据库优化器放弃使用索引,转而进行更低效的全表扫描。

索引失效的场景:

  1. 联合索引不满足最左匹配原则。
  2. 模糊查询最前面的为不确定匹配字符。
  3. 索引列参与了运算。
  4. 索引列使用了函数。
  5. 索引列存在类型转换。
  6. 索引列使用 is not null 查询。

1. 联合索引不满足最左匹配原则

原理:
联合索引(复合索引)是多个列上共同构建的一个索引,例如 INDEX idx_name_age (name, age)。这个索引的先按name排序,在name相同的情况下再按age排序。

最左匹配原则是指,查询条件必须从联合索引的最左列开始,并且不能跳过中间的列,才能充分利用该索引。

失效示例:

-- 表结构: user表,有联合索引 idx_name_age (name, age)-- 情况1:缺少最左列 `name`
SELECT * FROM user WHERE age = 25;-- 情况2:跳过了中间列 (假设索引是 (a, b, c))
SELECT * FROM user WHERE a = 'foo' AND c = 'bar'; -- 跳过了b,只能用a的部分索引

为什么失效?
因为索引的排序规则是先nameage。如果你只用age来查询,age在整个索引树里是无序的(因为先按name分了组),数据库无法利用索引的有序性进行快速查找,只能全表扫描。

特殊情况 - 索引下推(ICP):
在现代数据库(如MySQL 5.6+)中,即使跳过了中间列,如 WHERE a = 'foo' AND c = 'bar',数据库仍然会使用索引(a)进行初步筛选(a = 'foo'),然后再进行c = 'bar'的判断。这比完全不用索引好,但不如完全匹配所有列高效。

2. 模糊查询最前面的为不确定匹配字符

原理:
索引的有序性是基于列值的完整内容。如果查询条件的开头是通配符,就无法利用索引的有序性。

失效示例:

-- 假设在 `name` 字段上有索引
SELECT * FROM user WHERE name LIKE '%三'; -- 以'%'开头,索引失效
SELECT * FROM user WHERE name LIKE '%三%'; -- 以'%'开头,索引失效-- 不会失效的案例:
SELECT * FROM user WHERE name LIKE '张%'; -- 以确定字符开头,索引有效

为什么失效?
LIKE '张%'可以转化为范围查询(name >= '张' AND name < '丈'),可以利用索引的有序性快速定位。而LIKE '%三'要求查询所有以'三'结尾的值,这在索引树中是完全无序的,只能遍历所有数据。

3. 索引列参与了运算

原理:
索引中存储的是列的原始值。如果对列进行运算,数据库优化器无法将运算后的条件与索引中的原始值直接对应。

失效示例:

-- 假设在 `age` 字段上有索引
SELECT * FROM user WHERE age + 1 = 21; -- 索引失效
SELECT * FROM user WHERE YEAR(create_time) = 2023; -- 对列使用函数,也属于此类

为什么失效?
优化器不知道如何将 age + 1 这个条件反向计算出 age = 20,从而去索引里查找。它只能计算表中每一行的age + 1的值,然后判断是否等于21,这必然导致全表扫描。

解决方法:
将运算移到等号另一边,使用列的原生值。

SELECT * FROM user WHERE age = 20; -- 有效

4. 索引列使用了函数

原理:
这与“参与运算”的原理完全相同。索引存储的是原始值,而不是函数计算后的值。

失效示例:

-- 假设在 `create_time` (datetime类型) 字段上有索引
SELECT * FROM orders WHERE DATE(create_time) = '2023-10-26'; -- 索引失效
SELECT * FROM user WHERE LENGTH(name) = 2; -- 索引失效

为什么失效?
优化器无法通过 DATE(create_time) = '2023-10-26' 这个条件去索引树里查找,因为索引树是按 create_time 的原始值排序的,而不是按 DATE(create_time) 排序的。

解决方法:
使用范围查询代替函数。

SELECT * FROM orders
WHERE create_time >= '2023-10-26 00:00:00'
AND create_time < '2023-10-27 00:00:00'; -- 索引有效

5. 索引列存在类型转换

原理:
当操作符两边的数据类型不一致时,数据库会进行隐式类型转换(Implicit Cast),以使它们可以比较。这个转换过程相当于对列使用了函数。

失效示例:

-- 假设 `phone` 字段是字符串类型(varchar),并且有索引
SELECT * FROM user WHERE phone = 13800138000; -- 数据库会将phone列转为数字,索引失效-- 假设 `id` 字段是整数类型(int),并且有索引
SELECT * FROM user WHERE id = '123'; -- 数据库会将字符串'123'转为数字,索引仍然有效(原因看下方)

为什么失效?
WHERE string_column = number 会导致数据库调用 CAST(string_column AS SIGNED) 函数,将字符串列转为数字,这就回到了第4点“对列使用函数”,导致索引失效。

注意:
WHERE int_column = ‘123’ 通常不会导致索引失效,因为数据库会将字符串常量 ‘123’ 转换为数字 123,而不是对int_column列进行操作。关键是看转换是否作用于索引列本身

6. 索引列使用 is not null 查询

原理:
这个情况相对复杂,是否失效取决于数据库优化器的成本计算和表的具体情况。

  • 单列索引:在很多情况下,使用IS NOT NULL可以使用索引的,尤其是当索引本身具有高选择性(即NULL值很少)时。数据库可以通过遍历索引来跳过所有为NULL的条目。

  • 联合索引:如果联合索引的最左列查询条件是IS NOT NULL,由于其选择性太差(不符合最左匹配的精确查询),优化器可能认为全表扫描比回表查询大量数据更快,从而选择不使用索引。

总结:
IS NOT NULL 不一定会导致索引失效。优化器会评估使用索引的成本(需要回表的行数)和全表扫描的成本。如果表中大多数值都是NULL,用索引很快。如果几乎都不是NULL,那么用索引需要回表几乎所有的行,可能不如直接全表扫描。

失效示例(可能发生的情况):

-- 假设在 `address` 字段上有索引,但99%的记录都不为NULL
SELECT * FROM user WHERE address IS NOT NULL; -- 优化器可能选择全表扫描

总结与记忆技巧

可以将索引想象成一本按【姓氏+名字】排序的通讯录。

  1. 最左匹配:你无法快速找出所有叫“伟”的人,因为通讯录是先按“姓”排序的。

  2. 模糊前缀:你无法快速找出所有姓氏以“王”字结尾的人。

  3. 列运算/函数:你想找“姓氏长度等于1”的人,通讯录的目录帮不了你,你得自己翻。

  4. 类型转换:你把要找的“中文姓名”用拼音报出来,目录也无法直接匹配。

  5. IS NOT NULL:你想找出所有“电话号码不为空”的人。如果只有少数人没电话,用目录很快(跳过没电话的)。如果人人都有电话,用目录反而慢(得把所有人的号码看一遍),不如直接按顺序翻名单。

http://www.dtcms.com/a/366783.html

相关文章:

  • Java 双亲委派机制解析和破坏双亲委派的方式
  • T检验(pearman)
  • 【全网最全】《2025国赛/高教杯》C题 思路+代码python和matlab+文献 一到四问 退火算法+遗传算法 NIPT的时点选择与胎儿的异常判定
  • 电商金融贷款服务市场趋势与竞争分析
  • [frontend]WebGL是啥?
  • 鸿蒙NEXT交互机制解析:从输入设备到手势响应的全面指南
  • Node.js 18+安装及Claude国内镜像使用、idea中claude插件下载指南
  • 【AI论文】UI-TARS-2技术报告:借助多轮强化学习推进图形用户界面(GUI)智能体发展
  • Django事务
  • 《Docker 零基础入门到实战:容器化部署如此简单,运维效率直接拉满》
  • 【有鹿机器人自述】我在社区的365天:扫地、卖萌、治愈人心
  • Android集成OpenCV4实例
  • Java 与 Docker 的最佳实践
  • docker更新jar包,懒人执行脚本
  • MaxKB4j智能体平台 Docker Compose 快速部署教程
  • 飞算JavaAI全面解析:重塑Java开发流程的智能引擎
  • 【数学建模】用Matlab玩转图论:从画图到求最短路径
  • 想要给文档加密?2025年顶尖文件加密软件分享
  • C++并发编程-23. 线程间切分任务的方法
  • uniapp vue页面传参到webview.nvue页面的html或者另一vue中
  • Web应用:返回图片URL
  • Python快速入门专业版(一):Windows/macOS/Linux 系统环境搭建(附常见报错解决)
  • 【连接器专题】案例:带屏蔽膜FPC出现概率性短路,真是供应商的锅?
  • EasyVoice与cpolar:构建私域有声平台的本地化方案
  • Spring线程池ThreadPoolTaskExecutor‌详解
  • 隔空盗刷、AI钓鱼、代理劫持…金融黑产竟进化至此?
  • Elasticsearch 8 中 Nested 数据类型的使用方法
  • 【iOS】 懒加载
  • 一文吃透 CSS 伪类:从「鼠标悬停」到「斑马纹表格」的 30 个实战场景
  • 中值滤波、方框滤波、高斯滤波、均值滤波、膨胀、腐蚀、开运算、闭运算