返回文章列表
技术2026-03-15

数据库索引优化实战

从 B-Tree 原理到 EXPLAIN 分析,掌握数据库索引优化的核心技巧。

引言

索引是数据库性能优化的第一道防线。一个恰当的索引可以让查询从全表扫描(O(n))降到对数级查找(O(log n)),性能提升数千倍不是夸张。但错误的索引设计同样能让数据库性能雪崩。本文从 B-Tree 的内部结构讲起,带你理解索引优化的底层逻辑。

一、B-Tree 索引结构

大多数关系型数据库(MySQL InnoDB、PostgreSQL)默认使用 B+Tree 作为索引结构。B+Tree 是一种平衡多路搜索树,它的关键特性:

聚簇索引 vs 非聚簇索引

-- MySQL InnoDB:主键就是聚簇索引,叶子节点存储完整行数据
CREATE TABLE users (
  id INT PRIMARY KEY,        -- 聚簇索引:数据按 id 物理排序
  email VARCHAR(255),
  name VARCHAR(100),
  INDEX idx_email (email)    -- 二级索引:叶子节点存储主键值
);

-- 通过 email 查询的流程:
-- 1. 在 idx_email 中查找 email,得到主键 id
-- 2. 回表:用主键 id 在聚簇索引中查找完整行(这就是"回表")

二、EXPLAIN:读懂执行计划

优化索引的第一步是学会看执行计划。

-- PostgreSQL
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 123
  AND status = 'paid'
  AND created_at > '2026-01-01';

-- 关注的关键指标:
-- Seq Scan vs Index Scan:全表扫描 vs 索引扫描
-- Index Only Scan:覆盖索引扫描(最优,不需要回表)
-- Bitmap Index Scan:位图索引扫描(多个索引组合)
-- rows vs actual rows:估算行数 vs 实际行数(差距大说明统计信息过时)
-- cost:第一个值是启动成本,第二个值是总成本

MySQL EXPLAIN 关键字段

EXPLAIN SELECT * FROM orders WHERE user_id = 123;

-- type(访问类型):从好到差
-- const/system > eq_ref > ref > range > index > ALL
-- 目标至少是 range,最好是 ref 或 const

-- key:实际使用的索引
-- rows:扫描行数估算
-- Extra:Using index(覆盖索引)> Using where > Using filesort > Using temporary

三、复合索引的列顺序

复合索引(多列索引)的列顺序是优化中最容易被忽视的细节。

-- 假设最常见的查询是:
-- WHERE user_id = ? AND status = ? ORDER BY created_at DESC

-- 正确的索引顺序:等值条件在前,范围条件在后,排序字段在最后
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);

-- 错误的索引顺序:
CREATE INDEX idx_status_user_created ON orders(status, user_id, created_at);
-- 如果查询只带 user_id 不带 status,这个索引无法被使用

最左前缀原则

-- 索引:idx(a, b, c)
-- 可以使用索引的查询:
WHERE a = 1                    -- 使用 a
WHERE a = 1 AND b = 2          -- 使用 a, b
WHERE a = 1 AND b = 2 AND c = 3 -- 使用 a, b, c
WHERE a = 1 AND c = 3          -- 只使用 a(跳过 b 后无法继续)

-- 无法使用索引的查询:
WHERE b = 2                    -- 不满足最左前缀
WHERE b = 2 AND c = 3          -- 不满足最左前缀

四、覆盖索引

覆盖索引(Covering Index)是指查询所需的所有列都在索引中,不需要回表查询。

-- 需要回表的查询
SELECT * FROM orders WHERE user_id = 123;

-- 覆盖索引:查询的列都在索引中
CREATE INDEX idx_user_status_amount ON orders(user_id, status, amount);

SELECT user_id, status, amount
FROM orders
WHERE user_id = 123;  -- Extra: Using index(覆盖索引扫描)

五、索引何时适得其反

六、部分索引

-- PostgreSQL 部分索引:只索引符合条件的行
CREATE INDEX idx_active_orders ON orders(created_at)
WHERE status = 'active';

-- 这个索引只包含活跃订单,查询活跃订单时更快,且索引更小
SELECT * FROM orders WHERE status = 'active' AND created_at > '2026-01-01';

七、PostgreSQL vs MySQL 索引差异

总结

索引优化的核心原则很简单:让数据库尽可能少地扫描数据。具体来说就是:理解 B-Tree 结构知道为什么索引快,用 EXPLAIN 验证索引是否被使用,设计复合索引时遵循最左前缀原则,尽量使用覆盖索引避免回表,在写入和查询之间找到平衡点。记住,没有银弹索引——每个索引都是根据你的查询模式量身定制的。


返回文章列表
标签:数据库索引优化SQL