数据库索引的本质是用空间换时间的数据结构。没有索引时,数据库必须执行全表扫描(Full Table Scan),时间复杂度为O(N)。而借助合适的索引,查询复杂度可以降到O(log N)甚至O(1)。 但索引并非银剑——它们会降低写入速度、占用额外存储空间,并增加维护成本。理解B+树的底层结构,是做出正确索引设计决策的基础。 B+树是B树的变种,具有以下核心特征: InnoDB以页(Page)为最小I/O单位(默认16KB)。一个B+树节点刚好占用一页: 对于BIGINT(8字节)主键,一页大约可存储: 这意味着3层B+树可索引的记录数约为:1170 × 1170 × 16 ≈ 2200万条记录。 原则1:最左前缀匹配 联合索引 (a, b, c) 可支持 a / (a,b) / (a,b,c) 查询,但无法跳过前缀直接查 b 或 c。 原则2:选择性高的列优先 选择性 = 不同值总数 / 记录总数。越接近1越适合作为索引。性别字段选择性仅~0.5,不适合单独索引。 原则3:避免冗余索引 已有 (a,b) 联合索引时,单独的 (a) 索引是冗余的。使用 原则4:覆盖索引减少回表 当查询所需的所有列都在索引中时,引擎无需回表查询数据行。EXPLAIN显示 Using index。 原则5:索引用于WHERE和ORDER BY 索引可同时满足筛选和排序需求,避免 filesort。但注意排序方向需与索引一致。 原则6:小表不建索引 数据量少于几千行的表,全表扫描加内存过滤通常更快。索引本身带来维护开销。 现代优化器通过统计信息估算不同执行计划的成本: 关键字段含义: MySQL通过 原始查询: 索引 优化方案: 这看似没区别,但前者将触发列上的隐式CAST,使得 问题: 游标分页方案: 利用主键有序性,深翻页延迟从秒级降到毫秒级。 频繁UPDATE/DELETE导致页分裂和碎片。通过 开启慢查询日志并配合 关注以下InnoDB状态指标: 对表达式结果建索引,解决日期截断等场景: MySQL 8.0支持真正的降序索引: 将索引设为INVISIBLE用于安全测试,无需删除即可验证性能影响: 索引优化的核心是减少I/O次数。B+树的多路平衡特性使得千万级数据查询保持在3次I/O内。工程师应做到:数据库索引优化:B+树与查询计划的工程实践
一、为什么索引能加速查询?
二、B+树数据结构深度解析
2.1 基本结构
2.2 InnoDB的页式存储
+-------------------------------------------+
| 页头 | 键值1 | 指针1 | 键值2 | 指针2 | ... |
+-------------------------------------------+
| Page Header | key1 | child_ptr | key2 | child_ptr | ...
+---------------------------------------->
(16384 - 页头开销) / (8 + 6) ≈ 1170 个键三、索引设计六原则
pt-duplicate-key-checker 工具定期清理。四、查询优化器工作原理
4.1 基于成本的优化(CBO)
4.2 EXPLAIN执行计划解读
字段 含义 优化方向 type 访问类型 range > ref > index > ALL key 实际使用索引 确认命中预期索引 rows 预估扫描行数 越小越好 Extra 额外信息 关注Using filesort/Using temporary 4.3 统计信息更新
ANALYZE TABLE 更新统计信息。对于频繁更新的大表,建议在非高峰时段手动触发。InnoDB的持久化统计信息通过参数控制:innodb_stats_persistent=ON
innodb_stats_auto_recalc=ON
innodb_stats_persistent_sample_pages=128
五、实战:慢查询优化案例
5.1 案例:范围查询导致索引失效
SELECT * FROM orders WHERE create_time > 2024-01-01 ORDER BY id LIMIT 100;idx_create_time 存在但优化器选择了全表扫描——原因是create_time范围太大,回表成本过高。-- 创建覆盖索引避免回表
ALTER TABLE orders ADD INDEX idx_ct_id(create_time, id);
-- 或改用延迟关联
SELECT o.* FROM orders o
JOIN (SELECT id FROM orders WHERE create_time > 2024-01-01 ORDER BY id LIMIT 100) tmp
ON o.id = tmp.id;5.2 案例:隐式类型转换导致索引失效
-- phone为VARCHAR,但传入数字导致全表扫描
SELECT * FROM users WHERE phone = 13800138000;
-- 正确写法
SELECT * FROM users WHERE phone = 13800138000;idx_phone 索引完全失效。5.3 案例:分页深翻页优化
LIMIT 1000000, 20 需要扫描前100万行再丢弃。SELECT * FROM articles WHERE id > 999999 ORDER BY id LIMIT 20;六、索引维护与监控
6.1 索引碎片整理
OPTIMIZE TABLE 或 ALTER TABLE ... ENGINE=InnoDB 重建表可消除碎片。6.2 慢查询日志分析
pt-query-digest 分析热点SQL:slow_query_log=ON
long_query_time=0.5
log_queries_not_using_indexes=ON
6.3 性能监控指标
七、高级索引策略
7.1 函数索引(MySQL 8.0+)
CREATE INDEX idx ON orders((DATE(create_time)));7.2 降序索引
CREATE INDEX idx_ts ON metrics(ts DESC, device_id ASC);7.3 不可见索引
ALTER TABLE users ALTER INDEX idx_email INVISIBLE;八、总结

发表评论 取消回复