MySQL 复合索引怎么设计?从业务 SQL 反推索引顺序

索引不是越多越好,也不是"把 where 里的字段全塞进去"就结束。

这篇讲一个更实用的方法:从你的真实 SQL 反推索引。

先给一个可直接用的原则

设计复合索引时,按这个优先级排字段:

  1. 等值过滤字段(= / IN)
  2. 排序字段(ORDER BY)
  3. 范围字段(> < BETWEEN)
  4. 返回列(用于覆盖索引,按需补)

一句话:先过滤,再排序,最后才是范围和覆盖。


一、先看业务 SQL,再谈索引

假设订单列表最常见 SQL:

sql 复制代码
SELECT id, user_id, status, create_time
FROM orders
WHERE user_id = 10086
  AND status = 1
  AND create_time >= '2026-03-01 00:00:00'
ORDER BY create_time DESC, id DESC
LIMIT 20;

从这条 SQL 反推:

  • user_id、status:等值过滤,放前面
  • create_time、id:排序字段,接在后面

候选索引:

sql 复制代码
CREATE INDEX idx_orders_uid_status_ctime_id
ON orders(user_id, status, create_time DESC, id DESC);

二、为什么顺序这么重要

复合索引是按最左前缀匹配的,顺序错了,收益会掉很多。

比如你建成:

sql 复制代码
CREATE INDEX idx_bad ON orders(create_time, user_id, status);

那上面的 SQL 会先按 create_time 走,user_id/status 的过滤收益会被拖后,扫描行数往往更大。


三、3 个高频场景的索引模板

场景 1:列表查询(过滤 + 排序 + 分页)

SQL 特征:

  • WHERE a = ? AND b = ?
  • ORDER BY c DESC, id DESC
  • LIMIT ?

推荐:

sql 复制代码
INDEX(a, b, c DESC, id DESC)

场景 2:详情页查单条(精确定位)

SQL 特征:

  • WHERE biz_no = ? 或 WHERE tenant_id = ? AND biz_no = ?

推荐:

sql 复制代码
UNIQUE INDEX(tenant_id, biz_no)

能保证唯一性,也避免重复脏数据。

场景 3:统计报表(时间范围 + 分组)

SQL 特征:

  • WHERE tenant_id = ? AND create_time BETWEEN ? AND ?
  • GROUP BY status

推荐起步:

sql 复制代码
INDEX(tenant_id, create_time, status)

报表类还要结合数据量评估是否做离线聚合,别把在线库当数仓打。


四、你最容易踩的 6 个坑

  1. 把低选择度字段放最前 比如 gender/status/is_deleted 放第一列,过滤效果很弱。

  2. 只看 where,不看 order by 结果命中过滤却 Using filesort,耗时依然高。

  3. 索引过宽 字段太多会增大维护成本,写入变慢,内存占用更高。

  4. 重复索引 (a,b,c) 已经存在,再建 (a,b) 往往是冗余。

  5. 把范围字段放太早 一旦前面进入范围匹配,后续列利用效率会下降。

  6. 以为覆盖索引永远最好 覆盖索引确实快,但也更重;高写入表要平衡读写成本。


五、我自己的落地流程(5 步)

  1. 先拉一周慢 SQL,按调用量和耗时排序
  2. 选 TOP 场景逐条 EXPLAIN,确认 rows/key/extra
  3. 给每个核心 SQL 设计 1 个主索引(而不是每条 SQL 一个索引)
  4. 灰度上线,观察慢日志和写入延迟
  5. 清理冗余索引,避免"索引越优化越慢"

六、一个"改前改后"例子

慢 SQL:

sql 复制代码
SELECT *
FROM orders
WHERE status = 1
  AND user_id = 10086
ORDER BY create_time DESC
LIMIT 20;

问题:

  • SELECT * 回表重
  • 索引顺序不合理,排序额外开销大

改造:

sql 复制代码
-- 索引
CREATE INDEX idx_orders_uid_status_ctime
ON orders(user_id, status, create_time DESC);

-- SQL
SELECT id, user_id, status, create_time
FROM orders
WHERE user_id = 10086
  AND status = 1
ORDER BY create_time DESC
LIMIT 20;

通常你会看到:扫描行数下降、排序成本下降、P95 明显变稳。


最后总结

复合索引设计不是背口诀,而是把业务查询路径"翻译"成 B+Tree 顺序。

如果你只记一个句子,就记这个:

先拿慢 SQL,再反推索引;先服务高频场景,再追求理论完美。

相关推荐
程序猿DD43 分钟前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
余槐i3 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8264 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
凤山老林5 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne6 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF6 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li6 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
upala7 小时前
【仓颉语言入门 · 第二篇 第 6 课】
后端
罗丹丹Luna7 小时前
移动端显示“已提交”以后:SAP 与低代码集成该怎样暴露真实状态
后端·架构
码事漫谈7 小时前
因为太想讨好你,所以AI会骗你
后端