索引并非越多越好,尤其在频繁写入的报表表上,过多索引会拖慢INSERT/UPDATE/DELETE速度;应保留高选择性、覆盖WHERE/JOIN/ORDER BY的关键索引,移除冗余、低效或未使用的索引,并通过执行计划和统计信息持续优化。

索引不是越多越好,尤其在频繁写入的报表表上,过多索引会显著拖慢INSERT/UPDATE/DELETE速度——因为每写入一行,数据库都要同步更新所有相关索引。精简索引的核心是:保留真正被查询驱动的、高选择性的、能覆盖关键WHERE/JOIN/ORDER BY条件的索引,移除冗余、低效或长期未被使用的索引。
识别无效和冗余索引
先查哪些索引基本没被用过,再看哪些索引功能重复:
- 查未使用索引(以PostgreSQL为例):SELECT * FROM pg_stat_all_indexes WHERE idx_scan = 0 AND schemaname = 'public';
- 查重复索引(如已有 (a, b),又建了 (a, b, c),且c很少参与过滤,则后者可能冗余;或同时存在 (a) 和 (a, b),前者通常可删)
- 注意前缀匹配:索引 (user_id, create_time) 可支撑 WHERE user_id = ? 和 WHERE user_id = ? ORDER BY create_time,不必再单独建 (user_id)
聚焦高频查询路径建索引
报表类SQL往往有固定模式,比如按日期范围+状态汇总、按部门分组统计。只针对这些真实执行计划中显示“Index Scan”或“Bitmap Index Scan”的字段组合建索引:
- 把WHERE中最常出现的等值字段放索引最左列(如 status, report_date)
- 范围条件(如 report_date BETWEEN ? AND ?)尽量放索引末尾,避免阻断后续列的索引利用
- 如果查询含 GROUP BY dept_id, category 且常带 ORDER BY dept_id,考虑建 (dept_id, category) 覆盖索引
用覆盖索引减少回表
报表查询常需返回多个字段,但主键索引以外的二级索引默认只存索引列+主键。若能把SELECT涉及的字段都包含进索引(PostgreSQL用 INCLUDE,MySQL 8.0+支持函数索引和部分覆盖),就能避免回表读取整行数据:
- 例如查询:SELECT dept_id, SUM(amount), COUNT(*) FROM rpt_daily WHERE report_date >= '2024-01-01' GROUP BY dept_id;
- 可建:CREATE INDEX idx_rpt_dept_amt ON rpt_daily (report_date, dept_id) INCLUDE (amount);
- 这样索引本身就能满足过滤+分组+聚合,无需访问原表数据页
定期审查与渐进优化
索引优化不是一锤子买卖。上线后要持续观察:
- 开启慢查询日志,抓取执行时间 >1s 的报表SQL,分析其执行计划是否走索引、是否回表、是否有索引条件下推失败
- 每月用 pg_stat_statements(PG)或 performance_schema(MySQL)看索引使用频次和命中率
- 删除索引前先 SET enable_indexscan = off 模拟测试,确认删除后关键查询性能下降是否可接受










