数据库如何创建索引(数据库加索引为什么还慢?这 3 个细节最容易忽略)

数据库如何创建索引(数据库加索引为什么还慢?这 3 个细节最容易忽略)
数据库加索引为什么还慢?这 3 个细节最容易忽略



很多人一看到 SQL 慢,第一反应就是:

“给这个字段加个索引。”

结果索引加了,查询还是慢;
有时候甚至 EXPLAIN 一看,索引都用上了,但执行时间依旧不好看。

这时候问题往往不在“有没有索引”,而在于另一个更关键的点:

你以为你在优化索引,实际上你是在和查询方式、数据分布、执行路径打架。

我见过很多项目都是这样:

  • 表里明明有索引,还是全表扫
  • 走了索引,但扫描行数依然巨大
  • SQL 看起来很简单,实际慢点根本不在 where,而在排序、分页、回表、锁等待

所以,数据库慢,很多时候不是“没加索引”,而是对索引的理解还停留在“加了就会快”这个阶段

今天我想聊 3 个最容易被忽略的细节。
这 3 个点,基本上就是很多慢 SQL 的真正根源。


一、不是加了索引就行,而是你的 SQL 真正“用对了索引”没有

很多人说“这个字段我明明加了索引,为什么还是慢?”

先别急着怪数据库,先看一句最现实的话:

索引不是你建了就一定会用,只有你的 SQL 写法适合它,优化器才可能选它。

最常见的几个坑,几乎每个项目都见过。

1. 对索引列做了函数、计算、表达式处理

比如:

SELECT * FROM ordersWHERE DATE(create_time) = '2026-03-06';

看起来挺自然,但这类写法很容易让索引失效。
因为你不是在直接比较 create_time,而是先对列做了 DATE() 处理。

数据库想走索引,最喜欢的是“原值可直接定位”的条件。

数据库如何创建索引(数据库加索引为什么还慢?这 3 个细节最容易忽略)

更稳的写法应该是:

SELECT *FROM ordersWHERE create_time >= '2026-03-06 00:00:00'  AND create_time <  '2026-03-07 00:00:00';

这类范围查询,通常比在列上套函数更容易利用索引。


2. 隐式类型转换,把索引悄悄搞没了

例如字段是字符串:

phone VARCHAR(20)

但你这样查:

SELECT *FROM customerWHERE phone = 13800138000;

表面没问题,实际上数据库可能会做类型转换。
一旦发生隐式转换,索引使用效果就可能变差,甚至直接放弃。

尤其在业务系统里,这种问题太常见了:

  • 数据库字段是 varchar
  • Java 里传的是 Long
  • MyBatis / JPA 帮你拼出来看似正常
  • 最后执行计划却不如预期

这类问题很隐蔽,因为 SQL 能跑,结果也对,但性能就是不对。


3. 联合索引建了,但 where 条件顺序和使用方式不对

比如你建了一个联合索引:

INDEX idx_user_status_time (user_id, status, create_time)

你以为下面这些都会快:

WHERE user_id = ?WHERE status = ?WHERE create_time > ?WHERE status = ? AND create_time > ?

其实不是。

联合索引通常要看最左前缀
它更像是先按 user_id 排,再按 status 排,再按 create_time 排。

所以最有效的往往是这种:

WHERE user_id = ?AND status = ?AND create_time > ?

而如果你跳过前面的列,直接查后面的列,索引效果通常会大打折扣。

这一点很多人容易误判

不是说索引“没建对”,
而是你建的是一条路,SQL 却没按这条路走


二、就算走了索引,也不代表一定快

这是第二个特别容易误解的点。

很多开发一看 EXPLAIN 里有索引,就放心了。
但实际上:

“走索引”和“查询快”不是一回事。

走索引,只能说明数据库觉得“从这个入口进”更合适,
但进去了以后,是不是还要扫描一大堆数据、回表很多次、排序很多次,那是另一回事。


1. 区分度太低的索引,价值可能没你想的那么大

比如字段:

status

只有几个值:

  • 0:待处理
  • 1:已完成
  • 2:已取消

这时候你给 status 单独建索引,看起来没毛病。
但如果一张表有 500 万数据,其中 300 万都是 status = 1,那你查:

SELECT *FROM ordersWHERE status = 1;

即使走了索引,也可能要扫出大量记录。
这种情况下,索引的过滤能力其实很有限。

所以索引好不好,不是看“有没有”,而是看它能不能快速缩小结果集

一句话说透:

索引最怕的不是没有,而是“有了跟没有差不多”。


2. select * + 回表,是很多慢查询的老毛病

来看一个典型场景:

SELECT *FROM ordersWHERE user_id = 10001ORDER BY create_time DESCLIMIT 20;

你可能已经给 user_id 建了索引,甚至也走了索引。
但为什么还慢?

因为你查的是 *。

如果索引里没有你需要的全部字段,数据库通常要先从索引里找到主键,再回到主表里把整行数据取出来,这就是常说的回表

当返回数据很多,或者扫描候选行很多时,回表成本就会上来。

这也是为什么“覆盖索引”很重要

比如你真正只需要这些字段:

SELECT id, user_id, create_time, amountFROM ordersWHERE user_id = 10001ORDER BY create_time DESCLIMIT 20;

然后索引设计成:

INDEX idx_user_time_amount (user_id, create_time, amount)

这时很多数据库就有机会直接从索引里把结果拿出来,减少回表成本。

不是说所有 SQL 都必须追求覆盖索引,
但至少你要知道:大量 select *,本身就是性能优化的敌人。


3. 深分页,才是很多“明明走索引还慢”的真凶

再看一个业务里很常见的写法:

SELECT id, title, create_timeFROM articleORDER BY create_time DESCLIMIT 100000, 20;

这个 SQL 很多人第一眼会想:

“不是有索引吗?为什么还慢?”

问题在于 LIMIT 100000, 20 这种深分页,本质上不是直接跳到第 100001 条,而是前面的大量数据仍然要参与定位、排序、跳过。

你看到的是取 20 条,
数据库干的却可能是:先处理前面 100020 条,再丢掉其中 100000 条。

这时候瓶颈根本不在“有没有索引”,而在分页方式。

更适合大数据量场景的,是基于游标/上次位置继续翻页,例如:

SELECT id, title, create_timeFROM articleWHERE create_time < '2026-03-06 12:00:00'ORDER BY create_time DESCLIMIT 20;

这类方式通常比深分页更稳。


三、你以为是索引慢,实际上慢在索引外面

这是我觉得最值得警惕的一点。

很多时候,大家把问题都集中在 where 条件和索引上,
但真实的慢点,可能压根不在那里。

数据库查询慢,除了“找数据”,还有很多别的成本:

  • 排序
  • 临时表
  • 分组
  • 回表
  • 锁等待
  • 网络传输
  • 应用层拿太多数据
  • 统计信息不准导致执行计划选错

所以性能分析如果只停留在“加索引”,往往会越改越偏。


1. 排序没走上索引,可能比过滤更慢

例如:

SELECT *FROM ordersWHERE user_id = 10001ORDER BY update_time DESC;

你可能给 user_id 建了索引,也确实过滤变快了。
但如果 ORDER BY update_time 没法和索引顺序匹配,数据库可能还得额外排序。

真正慢的,不一定是 where,
而是后面的 filesort。

所以很多 SQL 优化,不是“多加一个索引字段”那么简单,
而是要整体看:

  • where 怎么过滤
  • order by 怎么排序
  • limit 怎么截取
  • 返回列有哪些

它本质上是一个完整执行路径的问题。


2. 锁等待,看起来像慢查询,其实不是查得慢

业务系统里还有一种情况特别坑:

SQL 本身不复杂,执行计划也不差,但它就是慢。

这时候你再去看数据库监控,发现可能不是 CPU 高,不是扫描行多,而是被锁住了

比如:

  • 一个大事务长时间不提交
  • 更新和查询互相等待
  • 热点行频繁修改
  • 批量操作把锁持有时间拖太长

这类场景下,SQL 慢只是表象,真正慢的是等待。

也就是说:

你以为在做索引优化,实际上应该先做事务治理。


3. 执行计划不是一成不变的,数据量一变,原来的“好索引”也可能变味

很多开发有个误区:

“我上个月看过这个 SQL 的执行计划,是走索引的,没问题。”

但数据库不是静态的。

随着数据量变化、分布变化、冷热数据变化,优化器的选择也可能变。
今天它觉得索引最优,明天它可能觉得全表扫更划算。

所以真正成熟的性能优化,不是“建完索引就结束”,而是:

  • 看执行计划
  • 看扫描行数
  • 看实际返回行数
  • 看慢日志
  • 看锁等待
  • 看数据分布是否变化

说白了,索引优化不是一次性动作,而是持续校准。


该怎么真正判断“为什么加了索引还慢”?

我自己更建议按下面这个顺序排查,而不是一上来就盲加索引。

第一步:先看 SQL 是否真的用上了索引

看 EXPLAIN,重点关注:

  • type
  • key
  • rows
  • Extra

尤其注意有没有这些信号:

  • Using where
  • Using filesort
  • Using temporary

如果索引都没选上,先别谈优化效果。


第二步:看“用了索引以后扫了多少”

很多 SQL 最大的问题不是“没走索引”,
而是“走了个没什么过滤能力的索引”。

这时候要看:

  • 扫描行数大不大
  • 返回行数多不多
  • 是否低区分度字段顶在前面
  • 是否存在大量回表

第三步:看慢点是不是在排序、分页、锁等待

不要把所有问题都归因到索引。
真正成熟的排查一定会继续看:

  • 是否深分页
  • 是否排序没命中索引
  • 是否临时表/分组过重
  • 是否事务过大
  • 是否有锁竞争

一个很实用的结论

我越来越觉得,数据库优化里最容易出问题的一点就是:

大家总在问“有没有索引”,却很少认真问“数据库为了这条 SQL 到底做了什么”。

索引只是入口。
而一次查询真正耗时的地方,可能在入口之后很远。

所以,“加了索引为什么还慢”,很多时候无非就这 3 类原因:

1. 索引没真正用对

函数、隐式转换、联合索引使用方式不对,都会让索引效果大打折扣。

2. 走了索引,但扫描和回表成本依然很高

低区分度、select *、深分页,都会让“有索引”变得意义有限。

3. 真正的慢点不在索引本身

排序、锁等待、临时表、执行计划变化,才是很多 SQL 变慢的根源。


最后

索引从来不是“加上就快”的开关,
它更像是一套交通规则。

你路修了,不代表车就一定跑得快;
方向不对、路口堵住、前面排队、出口设计有问题,照样快不起来。

所以做数据库优化,别只盯着“建索引”这一个动作。
真正重要的是:

看清楚这条 SQL 是怎么走的,慢到底慢在哪一段。

这才是从“会加索引”,走向“会做性能优化”的分水岭。


如果这篇内容对你有帮助,欢迎点赞 、收藏 ⭐、转发给需要的朋友

我会持续分享:

  • Java 核心与高阶实战
  • AI / Agent / 前沿技术落地
  • 真实项目经验 & 架构思考
  • 企业数字化与产品实践

关注我,一起把“技术”真正用在项目和业务里。

你的每一次支持,都是我持续输出高质量内容的最大动力。

#sql优化##苦逼创业记录##java开发##编程思想#

文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有

相关阅读

最新文章

热门文章

本栏目文章