索引是成本与性能的权衡艺术。
场景与痛点
这篇文章面向2-5 人小团队,从稳定性视角深入拆解MySQL 索引与查询优化。当前定位为「实战」阶段,核心目标是面向真实流量与团队协作。我们会从实际场景出发,结合具体代码示例,把关键知识点拆解为可落地的行动步骤。衡量标准:SLA/MTTR/错误率。
数据库是大多数应用的性能瓶颈所在。当数据量从几千条增长到几百万条时,一条没有索引的查询可能从毫秒级变成秒级。但索引不是越多越好——每个索引都会占用存储空间,并且在写入时需要额外维护。理解索引的工作原理和 EXPLAIN 的输出,是数据库优化的基本功。
当团队规模是2-5 人小团队时,最大的挑战不是”不会做”,而是”做了但不可复用、不可追溯”。在已有系统的重构期的背景下,我们需要一套既轻量又可靠的方案。
核心原理
MySQL 的 InnoDB 引擎使用 B+ 树作为索引结构。没有索引时,查询需要全表扫描(Full Table Scan),逐行检查是否满足条件。有了索引,查询可以通过树的层级快速定位到目标数据,时间复杂度从 O(n) 降到 O(log n)。对于百万级数据表,这意味着从扫描 100 万行变成只需要访问 3-4 个树节点。
分步实施指南
索引设计的核心原则:
- 联合索引遵循最左前缀原则。索引 (a, b, c) 可以加速 WHERE a=1、WHERE a=1 AND b=2、WHERE a=1 AND b=2 AND c=3 的查询,但不能加速 WHERE b=2 的查询。
- 覆盖索引避免回表。如果查询的字段都在索引中,MySQL 可以直接从索引返回数据,不需要再去主键索引查完整行。
- 索引列不要参与计算。WHERE YEAR(create_time) = 2024 无法使用 create_time 的索引,应改为 WHERE create_time >= ‘2024-01-01’ AND create_time < ‘2025-01-01’。
EXPLAIN 是分析查询性能的核心工具。重点关注:type 列(ALL 表示全表扫描,ref/range 表示使用了索引),rows 列(预估扫描行数),Extra 列(Using index 表示覆盖索引,Using filesort 表示需要额外排序)。
实战代码
以下代码片段经过简化,可以直接用于项目中:
-- 分析查询执行计划
EXPLAIN SELECT id, name, email
FROM users
WHERE tenant_id = 2 AND status = 1
ORDER BY created_at DESC
LIMIT 20;
-- 创建联合索引(匹配查询模式)
ALTER TABLE users
ADD INDEX idx_tenant_status_created (tenant_id, status, created_at DESC);
-- 优化前:全表扫描 type=ALL, rows=1000000
-- 优化后:索引范围扫描 type=range, rows=50
-- 查看慢查询统计
SELECT query, exec_count, avg_latency
FROM sys.statements_with_runtimes_in_95th_percentile
ORDER BY avg_latency DESC LIMIT 10;
进阶实践
建议建立慢查询治理流程:开启 slow_query_log,设置 long_query_time = 1(1秒以上记为慢查询)。每周 review 慢查询日志,为每条慢查询指定 owner 负责优化。优化后用 EXPLAIN 验证执行计划,确认索引生效。对于复杂查询,可以用 EXPLAIN ANALYZE(MySQL 8.0+)查看实际执行时间。
踩坑记录
常见错误:为每个 WHERE 条件单独建索引(应该建联合索引);索引列发生隐式类型转换(比如 VARCHAR 列用数字查询);SELECT * 导致无法使用覆盖索引;ORDER BY 和 WHERE 使用不同的索引导致 filesort;以及在低基数列(如性别、状态)上建索引,效果很差。
下一步行动
如果你正处于实战阶段,建议先把核心链路的SLA/MTTR/错误率监控建立起来,然后按照上面的步骤逐项推进。记住,降低故障率并缩短恢复时间不是一蹴而就的,而是持续迭代的过程。每次改进后都要回看数据,确认效果符合预期。