--- title: "数据库索引优化实战" date: 2026-05-10T16:00:00+08:00 draft: false description: "慢查询分析、联合索引的最左前缀原则、覆盖索引与回表的性能差异。几个真实 SQL 优化案例,从几秒降到毫秒级。" tags: - 数据库 - SQL - 性能 --- `EXPLAIN` 是 DBA 的眼睛。看 `type` 字段从 `ALL` 变成 `ref` 是最有成就感的事。 ## 从慢查询开始 一切优化从定位问题开始。打开 MySQL 的慢查询日志(`slow_query_log`),设置 `long_query_time = 0.1`,跑一段时间后分析。PostgreSQL 用 `pg_stat_statements` 扩展,同样有效。 拿到慢查询,先 `EXPLAIN` 看执行计划。关注几个关键字段: | 字段 | 含义 | 目标 | |------|------|------| | type | 访问类型 | 至少到 range,最好到 ref/const | | key | 使用的索引 | 不为 NULL | | rows | 扫描行数 | 越小越好 | | Extra | 额外信息 | 避免 Using filesort / Using temporary | ## 最左前缀原则 联合索引是最容易出错的地方。假设索引是 `(a, b, c)`: - `WHERE a = 1` ✅ 用到索引 - `WHERE a = 1 AND b = 2` ✅ 用到索引 - `WHERE b = 2` ❌ 用不到 - `WHERE a = 1 AND c = 3` ⚠️ 只用到了 a 列 排序也受这个规则约束。`ORDER BY a, b` 没问题,`ORDER BY b, c` 就需要额外排序。 ## 覆盖索引 如果查询需要的所有列都在索引里,就不需要回表取数据。`Extra` 列显示 `Using index` 就是覆盖索引。对一个高频查询,覆盖索引能减少大量随机 I/O。 代价是索引变大了。不要为了覆盖索引把所有列都塞进索引——算一笔写入性能和存储空间的账。 ## 真实案例 某个订单列表查询,原始 SQL 跑了 8 秒。`EXPLAIN` 一看,`type: ALL`,全表扫描 200 万行。加了 `(user_id, created_at)` 联合索引后,`type: ref`,扫描 20 行,耗时 12ms。 **索引不是越多越好**——每个索引都会拖慢写入速度并占用磁盘空间。建索引前问自己:这个查询的执行频率值得一个索引吗?