索引优化先从慢查询开始
索引不是越多越好。每增加一个索引,写入、更新和删除都要维护额外结构,占用磁盘并增加写入成本。因此优化第一步不是凭感觉加索引,而是找到真实慢查询。
排查慢查询时重点看三类信息:SQL 文本、执行频率、单次耗时。低频但耗时极高的查询会影响后台任务,高频但每次只慢一点的查询会拖垮接口响应。
执行计划要看什么
不同数据库的执行计划格式不同,但核心关注点类似:
- 是否全表扫描。
- 是否使用了预期索引。
- 扫描行数和返回行数差距是否过大。
- 是否出现额外排序或临时表。
- 连接顺序是否合理。
一个查询返回 20 行,但扫描了几十万行,通常说明过滤条件没有被有效利用。
选择性决定索引价值
选择性指字段能把数据区分开的程度。性别、状态这类字段取值少,单独建索引价值有限;用户 ID、订单号、邮箱这类字段区分度高,更适合索引。
低选择性:status = 'ACTIVE'
高选择性:user_id = 1024
高选择性:email = 'user@example.com'低选择性字段不是绝对不能进索引,而是通常要和其他字段组成复合索引。
复合索引顺序怎么定
复合索引要根据查询条件设计,不是简单把所有 where 字段放进去。常用原则:
- 等值条件优先。
- 区分度高的字段靠前。
- 范围查询字段后面的字段通常难以继续高效利用。
- 如果查询带排序,尽量让索引顺序同时满足过滤和排序。
例如订单列表常见查询:
SELECT * FROM orders
WHERE user_id = ? AND status = ?
ORDER BY created_at DESC
LIMIT 20;可以考虑:
CREATE INDEX idx_orders_user_status_created
ON orders(user_id, status, created_at DESC);这个索引先按用户过滤,再按状态过滤,最后按创建时间顺序读取,适合用户订单列表场景。
分页查询的坑
深分页是常见性能问题。下面这种查询在页数很深时会跳过大量数据:
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 20 OFFSET 50000;更稳的方式是使用游标分页,例如记录上一页最后一条的时间和 ID:
SELECT * FROM posts
WHERE (created_at, id) < (?, ?)
ORDER BY created_at DESC, id DESC
LIMIT 20;这种方式能利用索引继续向后扫描,避免越翻越慢。
常见误区
- 每个字段都建单列索引。多个单列索引不一定能替代一个合适的复合索引。
- 索引列上使用函数。比如对时间字段做函数转换,可能导致索引失效。
- 模糊查询前置通配符。
LIKE '%keyword'通常无法正常使用普通 BTree 索引。 - 只看单次执行时间,不看执行频率。
优化流程
- 收集慢查询和接口耗时。
- 查看执行计划,确认扫描行数和索引使用情况。
- 根据查询模式设计或调整复合索引。
- 在测试环境对比执行计划和耗时。
- 上线后观察写入性能、磁盘占用和慢查询变化。
索引优化的目标是让数据库少扫描、少排序、少回表。每个索引都应该对应明确的查询场景,否则就是未来的维护成本。
评论