本文目录导读:

- 场景一:高并发、低延迟的“点查询”
- 场景二:频繁的范围查询(Range Scan)
- 场景三:高频的“排序”或“分组”
- 场景四:多条件模糊查询(电商、搜索)
- 场景五:写多读少的“日志型”业务
- 场景六:物化视图 / 复杂聚合(OLAP)
- 总结:如何判断策略是否“适配”?
- 一个核心理念
这是一个非常专业且核心的数据库性能问题,答案是:绝对需要适配,没有放之四海而皆准的万能索引策略。
索引优化必须紧贴具体的数据特征(数据量、分布、写入频率)和业务查询模式(查询频率、查询条件、排序、聚合方式)。
下面我将从几种典型的业务场景出发,分析对应的核心索引优化策略,并指出常见的“不适配”坑。
高并发、低延迟的“点查询”
业务特征: 如用户查询订单详情、根据ID读取用户信息,SQL通常是 SELECT * FROM orders WHERE order_id = ?。
核心策略: 唯一索引 / 主键索引,利用B+树索引的二分查找特性,将查询复杂度降为O(log n),这类查询通常只需要命中少量数据行。
适配要点:
- 索引必须非常“聚簇”或覆盖(如果表使用了InnoDB,主键索引就是聚簇索引)。
- 坑: 在
order_id上建了一个多字段的联合索引(如idx_order_id_status),虽然也能用,但索引体积更大,可能不如单独的单列索引高效。
频繁的范围查询(Range Scan)
业务特征: 如查询“本月注册的用户”WHERE created_at BETWEEN ‘2024-01-01’ AND ‘2024-01-31’,或“价格在100-200之间的商品”。
核心策略: B+树索引的最左前缀匹配,联合索引中,范围列应放在最后。
- 联合索引
(status, created_at),如果你查status=1 AND created_at > ‘2024-01-01’,索引会先精确定位到status=1,然后在created_at上走范围扫描,非常高效。 - 不适配的坑: 把范围列放在联合索引的第一位,例如建了
(created_at, status),当你查status=1时,索引无法利用,会变成全表扫描。
高频的“排序”或“分组”
业务特征: ORDER BY created_at DESC 或 GROUP BY city_id。
核心策略: 利用索引的有序性避免文件排序(filesort),B+树索引本身就是有序的。
- 推荐索引:
(city_id, created_at),这样GROUP BY city_id时,数据物理上已按city_id排好序;对每个city_id,created_at也是有序的,直接输出即可。 - 不适配的坑: 查询条件中有
WHERE,但索引只建了排序字段。SELECT * FROM orders WHERE status=1 ORDER BY created_at,如果索引只是(created_at),数据库需要把所有status=1的行拿出来排序,推荐索引:(status, created_at)。
多条件模糊查询(电商、搜索)
业务特征: 如商品列表页,用户筛选:品牌=‘Apple’ AND 价格<5000 AND 评价数>1000 AND 名称 LIKE ‘iPhone%’。
核心策略: 基于等值条件的“前缀索引” + 谓词下推,绝对不能对所有可选条件都建独立索引。
- 最佳实践:找出选择性最高(cardinality)且必定被用到的列作为联合索引的前缀(如
(category_id, brand_id)),后面的字段(价格、评价数)交给数据库在内存中做谓词过滤(或使用“索引跳跃式扫描”)。 - 不适配的坑: 对
LIKE '%iPhone%'(前模糊匹配)建索引,这会导致索引完全失效,应对此类需求,应使用全文索引(FULLTEXT) 或 搜索引擎(Elasticsearch)。
写多读少的“日志型”业务
业务特征: 如埋点日志、操作日志,数据量极大(每天千万级),查询需求极少(仅用于排查)。 核心策略: 最少索引,或者单列索引,甚至不建索引。
- 因为每次写入都需要更新索引,会大大降低写入性能(导致Buffer Pool脏页刷写、索引分裂)。
- 如果必须查,可以建一个针对时间戳的索引来归档(如
created_at),并按天删除旧分区。 - 不适配的坑: 模仿OLTP系统,为每个字段建索引,会导致写入速度急剧下降,存储空间膨胀。
物化视图 / 复杂聚合(OLAP)
业务特征: 如月度销售报表:SELECT region, SUM(amount) FROM sales WHERE date >= ‘2024-01-01’ GROUP BY region。
核心策略: 覆盖索引(Covering Index) 或 列式存储(如ClickHouse)。
- 覆盖索引:将查询所需的所有列都放进索引中(如
(date, region, amount)),这样查询不需要回表,直接从索引树获取数据,IO压力骤减。 - 不推荐的策略:只建
date索引,数据库需要根据date索引查到所有主键,然后回表读取region和amount,如果命中几百万行,回表性能极差。
如何判断策略是否“适配”?
你可以通过以下三步快速评估:
- 分析SQL频率: 那个SQL的执行频率最高?那个SQL耗时最长?优先优化前20%的热点SQL。
- 分析执行计划(
EXPLAIN):- type列是否为
const/ref/range(好)还是ALL/index(差)? - Extra列是否有
Using filesort(需要优化排序)或Using temporary(需要优化分组)? - rows列扫描的行数是否远超预期返回的行数?
- type列是否为
- 评估写入代价: 如果一张表每秒写入超过1000行,且索引超过3个,需要考虑“索引过多”导致的写入压力和Redo Log、Undo Log膨胀。
一个核心理念
索引优化没有银弹,一个索引在这个场景下是“良药”,在另一个场景下可能就是“毒药”。好的索引策略是“为具体查询模式定制”的,而不是“把所有字段都塞进索引里”。