news 2026/6/25 21:38:58

1.3 java面试题:索引优化(以 MySQL InnoDB 为例)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
1.3 java面试题:索引优化(以 MySQL InnoDB 为例)

明白了,我理解错上下文了。你问的“索引优化”和“慢SQL优化”是纯 Java 面试中关于数据库(典型是 MySQL)的那部分内容,跟 ES 没关系。我重新用 Java 技术面的老练视角,把这两个核心点讲透。


一、索引优化(以 MySQL InnoDB 为例)

1. 索引的本质:空间换时间 + 最左前缀 + 有序结构

  • B+Tree索引:范围查询、排序、分组利器。
  • Hash索引:只等值查询,不支持范围。
  • 全文索引:模糊匹配场景,性能仍难控,通常让 ES 去干。

Java 开发要记住:你建的每个索引都要能推动查询走 最左前缀 并尽可能覆盖

2. 建索引的通用原则(面试必背)

-- 联合索引 (a,b,c),相当于创建了-- (a), (a,b), (a,b,c) 三个索引-- 单独查 b 或 c 不走索引CREATEINDEXidx_a_b_cONt(a,b,c);

Java 工程师在写代码时就要反向推算 SQL

  • where a=? and b>? and c=?
    → a 用到索引,b 走范围后c 不能再走索引(范围断掉)。
  • where a=? order by b
    → a 过滤后利用索引本身有序,不用 filesort
  • where a=? and c=? order by b
    → a 和 c 条件,但 b 缺位,排序会 filesort。

3. 哪些列不适合单独建索引

  • 区分度低的:性别(男/女)只有一半。
  • 频繁更新的:导致页分裂和重建。
  • 长字符串:可考虑前缀索引或哈希列。

4. 覆盖索引是 Java 性能的银弹

// MyBatis 示例:不要 select *@Select("SELECT id, status FROM orders WHERE user_id = #{userId}")List<Order>listOrders(@Param("userId")LonguserId);

建索引INDEX idx_uid_status (user_id, status),SQL 全程只读索引不回表,QPS 能高几倍。

5. 索引失效的经典场景(Java 写 SQL 时务必避开)

  • WHERE function(col) = ?col + 1 = ?:函数/运算破坏索引。
  • LIKE '%keyword%':左模糊不走索引,除非用全文搜索。
  • 隐式类型转换:varchar列用数字比较,导致索引失效。
  • OR 连接非索引列:WHERE a=1 OR b=2,如果 b 没索引,全表扫描。
  • NOT IN、!=、<> 大部分情况不走索引。

二、慢 SQL 优化(Java 项目实战术)

1. 定位慢 SQL 三板斧

  • 慢查询日志+mysqldumpslow
  • Performance Schema开标准备监控。
  • 线上实时SHOW FULL PROCESSLISTSELECT * FROM information_schema.processlist抓慢语句。

Java 端可用 Druid 连接池内置的监控:DruidStatFilter,直接打印慢 SQL(slowSqlMillis配置)。

2. 拿到慢 SQL 后第一件事:EXPLAIN

EXPLAINSELECT*FROMtWHEREa=1ANDb>2ORDERBYc;

关注字段:

  • type:从优到差system > const > eq_ref > ref > range > index > ALL。至少要到range
  • key:实际用的索引,为空则全表。
  • rows:预估扫描行数,过大就是信号。
  • Extra:出现Using filesortUsing temporary必须干掉。

3. 常见 Java 层性能杀手与解决方案

  • 循环查库(N+1):MyBatis 用select标签的collection做嵌套映射,或手动拼接 ID 列表一次IN查询。
  • 深分页LIMIT 1000000,20:改写为游标式查询,利用覆盖索引+子查询
    SELECT*FROMtJOIN(SELECTidFROMtWHERE...ORDERBYidLIMIT1000000,20)AStmpONt.id=tmp.id;
    或使用 ES 做分页,MySQL 只当存储。
  • 大数据量统计:离线走数仓,在线用预估Redis 计数,别在事务表里实时COUNT(*)
  • 复杂连表:拆解成多次简单查询,在 Java 里Map组装,数据库只做数据的存取。

4. Java 代码级防患于未然

  • 强制索引提示(DBA 一般不同意,但有时救急):
    SELECT*FROMtFORCEINDEX(idx_a)WHEREa=1;
  • 小结果集驱动大结果集:JOIN 时小表写左边(MySQL 优化器通常会选,但代码里保证更好)。
  • 批量操作:批量插入用foreach拼接,单次 500 条左右;批量更新用 CASE WHEN。
    <insertid="batchInsert">INSERT INTO t (id, name) VALUES<foreachcollection="list"item="item"separator=",">(#{item.id}, #{item.name})</foreach></insert>
  • 分库分表后,SQL 必须带分片键,否则全库全表扫描。

5. 架构层面的优化(简单一提)

  • 读多写少加缓存(Redis/本地缓存)。
  • 写多读少异步削峰(MQ + 批量写入)。
  • 大表历史数据归档。
  • 读写分离,主库写、从库读。

面试时,如果问“你做过哪些慢 SQL 优化”,老练的回答是:

“定位上,通过 Druid 慢日志+EXPLAIN 定位到一条深分页 SQL,type 是 ALL,Extra 有 filesort。优化方案是:先用覆盖索引减少回表,再改成分页游标模式,利用 JOIN 最小化扫描行数。最终扫描行数从 200 万降到 2 万,耗时从 5s 降到 50ms 以内。”

这样既有工具链,又有代码级方案,还有量化结果,面试官会觉得你是真操盘过。

你抓住的这个点特别好,很多开发者写代码时都不太理解这条 MyBatis 注解背后真正的性能含义。我用最简单的表结构把覆盖索引拆解清楚。


一、先看一个真实表格

假设有一张orders表:

CREATETABLEorders(idBIGINTPRIMARYKEY,user_idINT,statusTINYINT,amountDECIMAL(10,2),created_atDATETIME,INDEXidx_user_status(user_id,status)-- 联合索引);

索引idx_user_status就像一本只有 user_id 和 status 以及主键 id的小册子。


二、什么是“回表”

你的 MyBatis 代码如果这样写:

@Select("SELECT * FROM orders WHERE user_id = #{userId}")List<Order>listOrders(@Param("userId")LonguserId);

MySQL 的执行过程是:

  1. 先在idx_user_status里找到所有user_id = 100的索引记录,取出对应主键 id
  2. 因为SELECT *需要amountcreated_at等字段,这些不在索引里,所以 MySQL 必须拿着每个主键 id再回到主键索引(聚簇索引)里读完整行数据。
    → 这就是回表,一次查询可能产生大量随机 I/O,性能下降。

三、覆盖索引怎么避开回表

改为:

@Select("SELECT id, status FROM orders WHERE user_id = #{userId}")List<Order>listOrders(@Param("userId")LonguserId);

此时查询只需要idstatus两个字段。
巧了,idx_user_status这棵 B+Tree 里已经包含了user_idstatusid(主键被隐含携带)。
也就是说,索引叶子节点已经提供了查询所需全部数据,MySQL 根本不需要再回表,直接扫描索引就返回结果。
这就是覆盖索引(Covering Index),查询数据全部由索引“覆盖”。

在 EXPLAIN 的 Extra 列你会看到Using index,而不是NULLUsing where,这是直接信号。


四、性能差距到底有多大

举一个极端的类比:

  • 未覆盖:索引找到 100 万行,再回表 100 万次,大量磁盘随机读。
  • 覆盖:只顺序扫描索引叶子节点,可能全在内存里。

实际测试中,覆盖索引的 QPS 可以是原来的数倍到数十倍,特别是表宽、查询结果行数多时。


五、Java 开发如何刻意使用覆盖索引

  1. 拒绝SELECT *,哪怕开始设计时就需要多字段,也要时常审视是否某些场景可以只查小部分列。
  2. 建联合索引时把查询条件列和结果列组合在一起
    比如经常有查询SELECT id, status FROM orders WHERE user_id = ? ORDER BY created_at,可以尝试建INDEX (user_id, created_at, status)来实现覆盖与排序都走索引。
  3. 用 MyBatis 的结果映射只映射需要的字段,不要在实体里映射大字段。

面试时,只要你说出“覆盖索引就是查询列被索引完全包含,避免回表,执行计划 Extra 显示Using index”,再加上一个实际的代码对比,就非常有说服力了。

还有什么细节想深挖,直接问。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 21:35:01

分布式存储到底是个啥?我用5年踩坑经验给你讲透

分布式存储到底是个啥?我用5年踩坑经验给你讲透 做这行快5年了,从最早给客户搭单机存储,到后来处理PB级数据,我踩过的坑比吃过的饭还多。上周还有个小老弟问我:“分布式存储和普通硬盘有啥区别?” 这个问题看似简单,但真要说清楚,得从根上聊。 这篇文章写给两类人:一…

作者头像 李华
网站建设 2026/6/25 21:27:39

Sunshine游戏串流服务器:开源自托管架构与高性能部署方案

Sunshine游戏串流服务器&#xff1a;开源自托管架构与高性能部署方案 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine是一款开源的自托管游戏串流服务器&#xff0c;专为M…

作者头像 李华
网站建设 2026/6/25 21:19:00

Java 锁优化与偏向锁分析

Java 锁优化与偏向锁分析 在多线程编程中&#xff0c;锁是保证线程安全的重要手段&#xff0c;但不当的锁使用可能导致性能下降。Java 虚拟机&#xff08;JVM&#xff09;通过一系列锁优化技术提升并发性能&#xff0c;其中偏向锁&#xff08;Biased Locking&#xff09;是一项…

作者头像 李华
网站建设 2026/6/25 21:18:23

2024年Web安全入门实战指南:从零构建攻防思维与技能体系

1. 项目概述&#xff1a;为什么现在是从零学习Web安全的最佳时机&#xff1f;如果你在2024年还在犹豫要不要踏入Web安全这个领域&#xff0c;我的建议是&#xff1a;立刻开始。这不是一句空话。过去几年&#xff0c;数字化转型的浪潮席卷了几乎所有行业&#xff0c;从传统的电商…

作者头像 李华