加了个索引,SQL性能竟然慢了10倍!

大家好,我是苏三,又跟大家见面了。

前言

最近星球中有小伙伴问我:加了索引,SQL查询效率一定会提升吗?

答案是否定的。

让我想起了几年前查询订单的场景。

优化前下面这条SQL的查询耗时是0.5s。

ini 复制代码
SELECT * FROM orders WHERE user_id = 10086; 

添加了下面的索引:

scss 复制代码
CREATE INDEX idx_user ON orders(user_id);

再次执行上面的查询SQL语句,此时的耗时却是5.2s。

出现了非常神奇的一幕:加了索引,SQL查询性能反而慢了10倍。

作为一个踩过无数数据库性能坑的老司机,今天跟大家一起聊聊那些"加了索引反而更慢"的诡异场景。

最近准备面试的小伙伴,可以看一下这个宝藏网站(Java突击队):www.susan.net.cn,里面:面试八股文、场景题、面试真题、项目实战、工作内推什么都有

1. 索引失效

加了索引之后,你以为它在工作,其实它在摸鱼,因为它可能已经失效了。

1.1 最左前缀原则

如果查询条件的顺序不对,努力白费。

复合索引 (a, b, c) 生效的关键在于最左前缀匹配

用户表创建了名称、年龄和城市这三个字段的复合索引:

scss 复制代码
CREATE INDEX idx_user ON user(name, age, city);

✅ 有效:使用最左列 name

sql 复制代码
SELECT * FROM user WHERE name = '苏三';

✅ 有效:使用最左前缀 (name, age)

sql 复制代码
SELECT * FROM user WHERE name = '苏三' AND age = 30;

❌ 失效:跳过了最左列 name

sql 复制代码
SELECT * FROM user WHERE age = 30;

❌ 失效:未使用最左前缀

sql 复制代码
SELECT * FROM user WHERE city = '北京';

原理:复合索引的存储结构类似于电话簿(先按姓排序,再按名排序)。

如果跳过"姓"直接查"名",索引就失效了。

1.2 函数操作

函数操作会让索引瞬间失忆。

对索引列做计算、函数转换或类型转换,会导致索引失效:

❌ 失效:对索引列使用函数:

sql 复制代码
SELECT * FROM user WHERE YEAR(create_time) = 2023;

❌ 失效:隐式类型转换 (phone 是 varchar)

sql 复制代码
SELECT * FROM user WHERE phone = 13800138000; 

✅ 有效:避免函数操作

sql 复制代码
SELECT * FROM user WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';

可以使用BETWEEN...AND查询时间范围。

原理:索引存储的是列的原始值。对值进行修改后,数据库无法在索引树中定位原始值。

1.3 范围查询

范围查询(><BETWEEN)会截断复合索引中后续列的匹配:

索引 (age, salary)

sql 复制代码
SELECT * FROM employee 
WHERE age > 25        -- ✅ age 范围查询
  AND salary = 10000; -- ❌ salary 无法使用索引

会导致salary的索引失效。

原理age>25 匹配到的是一系列值(非精确值),数据库无法高效地对 salary 进行索引过滤。

最近建了一些工作内推群,各大城市都有,欢迎各位HR和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。加苏三的微信:li_su223,备注:所在城市,即可进群。

2. 索引维护成本

天下没有免费的午餐,索引是有额外的维护成本的。

2.1 DML 操作变慢

每一次写入都可能在负重前行。

每次 INSERTUPDATEDELETE 操作,数据库不仅要修改数据,还要维护相关索引。

无索引表插入 100w 行:1.2 秒。

有 5 个索引的表插入 100w 行:15.8 秒 (实测差距 10 倍以上)。

场景 :在写多读少的高并发场景下(如流水记录),索引反而会成为性能瓶颈。

2.2 索引占用空间

索引可能会给磁盘和内存带来双重压力。

每个索引都是一棵 B+ 树,存储完整的索引列值(或组合值)。大表的索引轻松占据几十GB空间。

MySQL中可以通过下面的SQL查看表索引大小:

less 复制代码
SELECT 
  table_name AS `Table`,
  index_name AS `Index`,
  ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) `Size(MB)`
FROM mysql.innodb_index_stats 
WHERE table_name = 'your_table';

后果:索引过大导致内存中缓存命中率降低,物理 I/O 增加。

3. 优化器的"错误"选择

有时候,可能会出现聪明反被聪明误,加了索引可能会导致性能变慢。

3.1 统计信息过时

导航用了旧地图。

优化器依赖统计信息(如索引区分度、数据分布)来选择索引。

如果统计信息过期,优化器可能选择性能更差的索引。

MySQL中我们可以通过下面的命令强制更新表统计信息:

sql 复制代码
ANALYZE TABLE user;

场景:当表中数据发生剧烈变化(如大批量删除/导入)后,统计信息未及时更新。

3.2 回表代价高昂

索引再好也怕"绕路"。

假如给user表给age创建了索引:

sql 复制代码
SELECT name, email FROM user 
WHERE age > 25;

即使 age 索引被使用,数据库仍需根据索引中的主键 ID 回表查询 nameemail 字段。

当符合条件的数据量很大时,回表 I/O 可能远超索引扫描本身。

优化方案 :使用覆盖索引(Covering Index),让索引包含查询所需的所有列。

创建覆盖索引 (包含 age, name, email):

scss 复制代码
CREATE INDEX idx_age_covering ON user(age, name, email);

查询可直接从索引获取数据,无需回表

sql 复制代码
SELECT name, email FROM user WHERE age > 25;

4. 索引过多

当表上存在多个索引时,优化器需要评估每个索引的成本,选择越多,决策时间越长

下面的查询可能使用索引 A 或索引 B

ini 复制代码
SELECT * FROM orders 
WHERE user_id = 1001 
  AND status = 'completed';

优化建议

  1. 删除重复或冗余索引
  2. 合并可组合的索引
  3. 使用工具分析索引使用率(如 sys.schema_unused_indexes

5. 锁与并发

锁的竞争是一个看不见的战场。

5.1 行锁升级

在事务中通过索引检索并锁定行时,如果锁数量过多 (超过阈值),数据库可能将锁升级为表锁,严重降低并发性能。

场景:全表更新或删除大量数据时,索引的存在可能导致锁升级。

5.2 索引分裂的阻塞

B+树索引在插入数据时可能发生页分裂

这个过程需要加锁,在高并发写入场景下可能导致短暂阻塞。

总结

索引是把双刃剑,用对场景是关键。

  1. 理解原理:掌握最左前缀、索引失效条件、覆盖索引等核心机制
  2. 权衡成本:在写密集场景谨慎添加索引,评估维护代价
  3. 精准设计:按实际查询模式设计复合索引,避免冗余
  4. 关注统计信息:定期更新统计信息,确保优化器决策准确
  5. 监控分析 :使用 EXPLAIN、慢查询日志等工具持续跟踪索引效果

某电商平台曾因在流水表上盲目添加索引,导致高峰时段写入延迟飙升。

后经分析,移除两个非核心索引,写入速度提升8倍,而相关查询仅增加20毫秒------这正是索引取舍的艺术。

记住:索引不是越多越好,而是越准越好

真正的高手,懂得在索引的利刃上优雅行走。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

相关推荐
风象南1 小时前
SpringBoot实现简易直播
java·spring boot·后端
这里有鱼汤1 小时前
有人说10日低点买入法,赢率高达95%?我不信,于是亲自回测了下…
后端·python
武子康2 小时前
Java-39 深入浅出 Spring - AOP切面增强 核心概念 通知类型 XML+注解方式 附代码
xml·java·大数据·开发语言·后端·spring
米粉03052 小时前
SpringBoot核心注解详解及3.0与2.0版本深度对比
java·spring boot·后端
一只帆記3 小时前
SpringBoot EhCache 缓存
spring boot·后端·缓存
yuren_xia6 小时前
Spring Boot中保存前端上传的图片
前端·spring boot·后端
JohnYan9 小时前
Bun技术评估 - 04 HTTP Client
javascript·后端·bun
shangjg39 小时前
Kafka 的 ISR 机制深度解析:保障数据可靠性的核心防线
java·后端·kafka
青莳吖10 小时前
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
前端·spring boot·后端
我的golang之路果然有问题11 小时前
ElasticSearch+Gin+Gorm简单示例
大数据·开发语言·后端·elasticsearch·搜索引擎·golang·gin