欢迎光临
我们一直在努力

Java进阶必修课:为什么你建了索引,SQL 还是很慢?

很多 Java 开发在工作几年后,都会遇到一个很真实的问题:

明明给表建了索引,为什么接口还是慢?
明明 EXPLAIN 里显示走了索引,为什么 QPS 还是上不去?
明明只是查个订单列表,为什么数据库 CPU 会突然飙高?

如果你也有类似困惑,那么这篇文章想讲的不是“索引是什么”,而是更重要的一件事:

作为 Java 后端工程师,怎么从“会建索引”进阶到“真正会用索引”。


1. 索引不是银弹,它只是帮你缩小查找范围

很多人第一次接触索引,会把它理解成“加了索引就一定快”。

这个理解不算错,但太粗糙。

索引的本质,不是直接帮你拿到结果,而是更快地缩小数据范围。
对于 InnoDB 来说,大部分索引底层依赖 B+Tree,它擅长的是:

  • 精确查找
  • 范围查找
  • 有序访问
  • 这也就决定了:你的 SQL 是否“符合索引的使用习惯”,比“有没有索引”更重要。


    2. Java 开发必须理解的 3 个索引核心概念

    2.1 聚簇索引和二级索引

    InnoDB 的主键索引是聚簇索引,数据行本身就存储在主键索引叶子节点上。

    普通索引则是二级索引,叶子节点存的是:

    • 索引列值
    • 主键值

    这意味着一个常见性能问题:

    通过普通索引查到主键后,还要再回主键索引取整行数据,这个过程就叫“回表”。

    如果回表次数太多,性能就会明显下降。


    2.2 覆盖索引为什么这么重要

    如果一个查询需要的列,刚好都在索引里,那么数据库就不需要回表了,这就是覆盖索引。

    例如:

    SELECT id, status FROM orders WHERE user_id = 1001;

    如果索引是:

    CREATE INDEX idx_user_id_status ON orders(user_id, status);

    那么这条 SQL 在很多场景下就可能形成覆盖索引,性能通常会比 SELECT * 更好。

    这也是为什么在 Java 项目里,我一直建议:

    能明确字段就不要写 SELECT *。

    这不只是代码规范问题,更是性能问题。


    2.3 最左前缀原则不是面试题,是生产规则

    假设你有一个联合索引:

    CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);

    那么下面这些查询,索引利用情况完全不同:

    WHERE user_id = ?

    WHERE user_id = ? AND status = ?

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

    WHERE status = ?

    前三种通常能较好利用索引,最后一种大概率不行。

    原因就是联合索引遵循最左前缀原则。

    联合索引列顺序,必须围绕真实查询条件来设计。


    3. 为什么你建了索引,SQL 还是不走?

    这是 Java 项目里最常见的误区区。

    3.1 在索引列上做函数运算

    SELECT * FROM orders WHERE DATE(create_time) = '2026-06-04';

    看起来很自然,但这样会让索引失效。

    更好的写法是:

    SELECT * FROM orders WHERE create_time >= '2026-06-04 00:00:00' AND create_time < '2026-06-05 00:00:00';


    3.2 隐式类型转换

    比如数据库字段是 varchar,Java 代码里却传了数字参数;或者数据库字段是数字,SQL 却按字符串去比。

    这类问题在 MyBatis、JPA 项目里非常常见,而且很隐蔽。
    有时候你以为是索引设计有问题,实际上是参数类型不一致导致优化器放弃了理想执行计划。


    3.3 LIKE '%xxx' 前置模糊匹配

    WHERE name LIKE '%java%'

    这种写法很难有效利用 B+Tree 索引,因为树是从左到右有序的。

    如果业务允许,尽量改成:

    WHERE name LIKE 'java%'

    如果必须做全文检索,就该考虑搜索引擎方案,而不是强压 MySQL 普通索引。


    3.4 范围条件后面的列,利用率会下降

    还是这个联合索引:

    (user_id, status, create_time)

    当 SQL 变成:

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

    一旦中间列出现范围查询,后续列的索引利用通常就会受影响。

    所以联合索引的设计原则通常是:

    高频等值条件在前,范围条件靠后。


    4. 一个真实的 Java 接口优化案例

    假设我们有一个订单列表接口:

    public List<Order> queryOrders(Long userId, Integer status, int pageNo, int pageSize) { return orderMapper.queryOrders(userId, status, pageNo * pageSize, pageSize); }

    对应 SQL:

    SELECT * FROM orders WHERE user_id = #{userId} AND status = #{status} ORDER BY create_time DESC LIMIT #{offset}, #{pageSize};

    很多系统慢就慢在这里。

    问题有两个:

  • SELECT * 容易导致大量回表
  • 深分页 LIMIT 100000, 20 会扫描大量无效数据

  • 5. 正确的优化思路,不只是“补一个索引”

    先建联合索引:

    CREATE INDEX idx_user_status_ctime ON orders(user_id, status, create_time);

    然后改 SQL,只取必要字段:

    SELECT id, order_no, amount, create_time FROM orders WHERE user_id = #{userId} AND status = #{status} ORDER BY create_time DESC LIMIT #{offset}, #{pageSize};

    如果分页很深,再进一步优化成“延迟关联”:

    SELECT o.* FROM orders o JOIN ( SELECT id FROM orders WHERE user_id = #{userId} AND status = #{status} ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} ) t ON o.id = t.id;

    这种写法的核心思想是:

    先利用索引快速定位主键,再回表取完整数据,把大范围扫描的成本压缩到最小。

    这类优化,在订单、评论、消息、日志等大表场景里非常常见。


    6. Java 工程师做索引设计时,最容易犯的 4 个错误

    6.1 按字段名建索引,不按查询场景建索引

    索引是为 SQL 服务的,不是为表结构服务的。
    你要先看接口怎么查,再决定索引怎么建。

    6.2 一个字段一个索引,以为数据库会自动完美组合

    MySQL 并不会总是把多个单列索引组合成你想要的效果。
    很多时候,一个设计合理的联合索引,远比几个单列索引更有效。

    6.3 区分度太低的字段也强行建索引

    像性别、布尔状态、删除标记这类字段,区分度很低。
    单独建索引意义通常不大,更适合放到联合索引中。

    6.4 只看“走没走索引”,不看扫描行数

    真正该关注的不是 type = ref 还是 range 这么简单,
    而是:

    • rows 扫描了多少
    • 是否回表
    • 是否出现 Using filesort
    • 是否出现 Using temporary

    会看 EXPLAIN,才算真正进入索引优化的门。


    7. 我总结的一套索引设计口诀

    送你一套非常适合 Java 开发落地的经验口诀:

    等值在前,范围在后,排序跟上,字段别多,能覆盖就覆盖,能不回表就不回表。

    翻译成工程语言就是:

    • 优先围绕高频查询设计联合索引
    • 等值过滤字段优先放前面
    • 范围查询字段尽量放后面
    • ORDER BY 尽量和索引顺序一致
    • 查询字段尽量精简,争取覆盖索引
    • 不要为了“保险”滥建索引,因为索引也会拖慢写入

    8. 最后说一句:索引能力,是 Java 工程师的分水岭

    初级开发写功能,关注的是“能不能查出来”。
    中级开发做优化,关注的是“为什么查得慢”。
    高级开发做系统,关注的是“这个查询未来数据量涨 10 倍后还能不能扛住”。

    而索引,恰恰就是这条成长路径上绕不过去的一关。

    你会发现,真正优秀的 Java 工程师,未必天天手写复杂 SQL,
    但一定知道:

    什么时候该建索引,为什么这么建,以及 SQL 为什么没有按预期工作。

    这才是从 CRUD 走向工程能力的关键一步。

    赞(0)
    未经允许不得转载:171主机测评 » Java进阶必修课:为什么你建了索引,SQL 还是很慢?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址