本文目录导读:

- 核心原则:索引失效的常见场景(反模式排查)
- 规范化的索引生效流程(DDL + SQL + Application)
- 完整生效流程示例(Java + MySQL)
- 特殊情况:全文索引(Elasticsearch / MySQL Fulltext)
- 日常开发规范清单
在Java(特别是结合数据库如MySQL、PostgreSQL,或结合Elasticsearch等搜索技术)中,索引生效流程的规范性直接决定了查询性能,这里的“规范”通常指:确保索引被正确创建、被查询优化器正确使用、且不会因数据变更或DDL操作而失效。
以下从 数据库索引(以MySQL InnoDB为例,最典型)和 Java应用层(如JPA/Hibernate慢查询治理)两个维度,梳理索引生效的规范性流程。
核心原则:索引失效的常见场景(反模式排查)
索引不是创建了就会自动生效,规范流程的第一步是避免索引失效,以下是Java + MySQL中最常见的索引失效场景:
- 隐式类型转换:
WHERE varchar_col = 123(Java传入数字,SQL中字段是字符串,索引失效)。 - 函数操作:
WHERE DATE(time_col) = '2024-01-01'(在索引列上使用函数)。 - LIKE 通配符前缀:
WHERE name LIKE '%张三'(%放在前面)。 - OR 条件:
WHERE a = 1 OR b = 2(如果a和b不是都独立有索引,可能导致全表扫描)。 - 联合索引最左前缀失效:联合索引
(a, b, c),查询条件只用到b或c(跳过了a)。 - 不等号(!=, <>):通常会导致索引失效(部分数据库优化器会走全表扫描)。
- IS NULL / IS NOT NULL:取决于数据库版本和列属性,可能部分生效。
- 数据量小:优化器认为全表扫描比回表更快,主动放弃索引。
规范化的索引生效流程(DDL + SQL + Application)
DDL 设计规范(建表/加索引)
- 明确索引类型:区分普通索引、唯一索引、联合索引、全文索引。
- 联合索引顺序:选择性最高的列放最左侧(去重率越高越优先)。
- 避免冗余索引:
(a, b)和(a, b, c)是冗余的(前者可以覆盖后者最左前缀)。 - 使用前缀索引:对字符串(varchar(255))长文本,
INDEX idx_name(name(10))。
SQL 编写规范(查询侧)
- 强制走指定索引(兜底手段):
SELECT * FROM user FORCE INDEX(idx_user_name) WHERE name = '张三';
- 避免在索引列上计算:
-- 错误 WHERE YEAR(create_time) = 2024 -- 正确(范围查询) WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01'
- Join 字段字符集一致:MySQL 跨表 Join 时,如果两个表的字段字符集不同(如utf8 vs utf8mb4),索引会失效。
Java 应用层规范(JPA / MyBatis)
- MyBatis 动态SQL:确保
<if test>不会生成WHERE 1=1加上OR导致索引失效。 - 避免 N+1 查询:使用
@EntityGraph或@BatchSize或手动关联查询(而不是循环内查单条)。 - 分页查询:使用
LIMIT配合索引排序(避免ORDER BY走文件排序)。
验证流程(必须的规范动作)
每一步加完索引后,必须通过 EXPLAIN 验证是否真正走索引。
EXPLAIN SELECT * FROM user WHERE name = '张三';
关键字段:
type:const>ref>range>index>ALL(尽量达到ref或range)key:显示实际使用的索引名(如果为NULL表示没走索引)rows:扫描行数(期望远小于总行数)Extra:避免出现Using filesort、Using temporary、Using where; Using index(索引覆盖是好的)
完整生效流程示例(Java + MySQL)
场景:用户表按 phone 和 status 查询
-- 1. DDL 创建联合索引(phone选择性好放左侧)
ALTER TABLE user ADD INDEX idx_phone_status (phone, status);
-- 2. Java 代码编写(确保传入类型与数据库一致)
// 正确:phone是varchar,Java传入String
String phone = "13800138000";
Integer status = 1;
// 错误隐患:如果phone是varchar,传入Long类型会隐式转换
// Long phone = 13800138000L; -- 导致索引失效!
// MyBatis Mapper 写法
// <select id="findByPhoneAndStatus">
// SELECT * FROM user
// WHERE phone = #{phone} AND status = #{status}
// </select>
验证步骤:
EXPLAIN SELECT * FROM user WHERE phone = '13800138000' AND status = 1; -- 期望结果:type = ref, key = idx_phone_status, rows = 1
监控与告警(Java侧)
- 使用 Aliyun RDS 慢查询日志 或 p6spy(JDBC代理)自动打印SQL执行计划。
- 配置
Druid或HikariCP的慢查询阈值(slowSqlMillis=500)。 - 在 Java 代码层 添加 AOP 切面,记录执行时间超过阈值的 SQL,并人工审查索引情况。
特殊情况:全文索引(Elasticsearch / MySQL Fulltext)
如果是 ES 或 Solr 场景,索引生效的规范包括:
- Mapping 前:明确字段是否需要分词(
keywordvstext)。 - 查询类型:
term(精确匹配走索引) vsmatch(分词后走索引)。wildcard前缀模糊匹配会导致性能问题。 - 禁止搜索 或
_all字段(大量字段盲搜)。 - 使用
explain API查看打分和命中情况。
日常开发规范清单
| 阶段 | 规范动作 | 检查点 |
|---|---|---|
| 建表 | 评估查询字段,建联合索引 | 选择性高的列放左侧,避免冗余索引 |
| 编码 | SQL 避免函数、隐式转换、LIKE前缀% | 使用 MyBatis <trim> 或 @Query 指定参数类型 |
| 测试 | 加索引后运行 EXPLAIN |
type ≠ ALL,key 不为空,rows 小 |
| 发布 | 同步索引 DDL 给 DBA 审核 | 线上环境先 pt-online-schema-change 无损加索引 |
| 运维 | 开启慢查询日志 >1s 的记录,周报分析 | 每周抽检 Top 10 慢 SQL,做索引优化 |
最终铁定律:索引不生效的原因,90% 是 Java 代码传参类型错误 或 SQL 写法破坏了索引规则。 建议在团队内统一使用
EXPLAIN作为代码审查的必过项。