在 MySQL 中创建索引是提升查询性能的关键手段,但不当的创建会带来资源浪费、写入性能下降甚至优化器选错索引等问题。以下是创建索引时必须注意的核心事项:
一、索引设计阶段注意事项
-
明确目标查询 (WHERE, JOIN, ORDER BY, GROUP BY)
- 只为高频、关键、性能瓶颈的查询创建索引。
- 使用
EXPLAIN
分析查询执行计划,确认是否真正需要新索引。
-
遵循最左前缀匹配原则 (Leftmost Prefixing)
- 对于 组合索引
(col1, col2, col3)
:- 有效查询:
col1
、col1 AND col2
、col1 AND col2 AND col3
。 - 无效查询:
col2
、col3
、col2 AND col3
(无法使用索引)。
- 有效查询:
- 将区分度高(唯一值多)的列放在组合索引左侧。
- 对于 组合索引
-
优先考虑覆盖索引 (Covering Index)
- 让索引包含查询所需的所有字段(
SELECT
、WHERE
、JOIN
列),避免回表查询。 - 示例:查询
SELECT name, age FROM users WHERE city='北京'
→ 创建INDEX (city, name, age)
。
- 让索引包含查询所需的所有字段(
-
控制索引数量,避免冗余
- 单表索引不宜过多(通常 ≤5个)。每个索引占用磁盘空间,降低
INSERT/UPDATE/DELETE
速度。 - 检查是否有功能重叠的索引:
- 已有
INDEX(a,b)
,再建INDEX(a)
是冗余的(前缀索引可复用)。 - 用工具如
pt-duplicate-key-checker
检测冗余索引。
- 已有
- 单表索引不宜过多(通常 ≤5个)。每个索引占用磁盘空间,降低
-
选择区分度高的列
- 区分度 =
COUNT(DISTINCT col) / COUNT(*)
。值越接近 1,索引效果越好(如唯一ID)。 - 避免为区分度极低的列建索引(如性别、状态标志),优化器可能直接忽略索引。
- 区分度 =
二、索引创建时的技术细节
-
前缀索引优化长文本
- 对
VARCHAR
/TEXT
列,使用前缀索引节省空间:CREATE INDEX idx_name ON table (column(10)); -- 只索引前10字符
- 关键:计算合适长度,确保区分度:
SELECT COUNT(DISTINCT LEFT(column, 10)) / COUNT(*) AS selectivity FROM table;
- 对
-
为排序/分组优化索引
ORDER BY
或GROUP BY
子句的列顺序需与索引一致,且方向相同(全 ASC 或全 DESC)。- 示例:
SELECT ... ORDER BY a DESC, b DESC
→ 建INDEX(a DESC, b DESC)
。
-
谨慎处理 NULL 值
- 索引通常不存储
NULL
(除非是唯一索引)。查询WHERE col IS NULL
可能无法使用索引。 - 考虑用默认值(如
''
或0
)替代NULL
,或使用FULLTEXT
/SPATIAL
索引。
- 索引通常不存储
-
函数和表达式导致索引失效
- 索引无法用于函数计算的列。
失效示例:WHERE YEAR(create_time) = 2023
优化方案:WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'
- 索引无法用于函数计算的列。
-
隐式类型转换陷阱
- 列与查询值类型不一致时,索引失效。
失效示例:user_id
是字符串类型,但查询WHERE user_id = 100
(整数)。
- 列与查询值类型不一致时,索引失效。
三、生产环境操作注意事项
-
使用 Online DDL (MySQL 5.6+)
- 加
ALGORITHM=INPLACE, LOCK=NONE
减少锁表时间:CREATE INDEX idx_name ON big_table (column) ALGORITHM=INPLACE, LOCK=NONE;
- 注意:某些操作仍需锁表(如修改列类型、删除主键)。
- 加
-
避开业务高峰期
- 大表建索引可能耗时分钟级甚至小时级,阻塞写入操作。
-
监控资源消耗
- 创建过程中监控 CPU、I/O、内存使用,避免拖垮数据库。
-
优先在从库创建测试
- 在从库创建索引验证效果,确认无误再操作主库。
四、创建后的维护与验证
-
分析索引使用情况
- 查询
sys.schema_index_statistics
或INFORMATION_SCHEMA.STATISTICS
:SELECT * FROM sys.schema_index_statistics WHERE table_schema = 'your_db' AND table_name='your_table';
- 删除长期未使用的索引 (
index_reads=0
)。
- 查询
-
定期更新统计信息
- 优化器依赖统计信息选择索引。大表更新后执行:
ANALYZE TABLE your_table;
- 优化器依赖统计信息选择索引。大表更新后执行:
-
注意索引碎片化
- 频繁更新的表会导致索引碎片,定期优化:
OPTIMIZE TABLE your_table; -- 重建表及索引 ALTER TABLE your_table ENGINE=InnoDB; -- 仅InnoDB有效
- 频繁更新的表会导致索引碎片,定期优化:
常见索引失效场景总结
场景 | 解决方案 |
---|---|
组合索引未用最左列 | 调整查询条件或索引顺序 |
对索引列使用函数/表达式 | 重写查询,避免列参与计算 |
隐式类型转换 | 确保查询值与列类型一致 |
OR 连接非索引列 | 改为 UNION 或调整索引 |
LIKE '%value' 左模糊 | 考虑全文索引或改用右模糊 'value%' |
优化器误选索引 | 用 FORCE INDEX 或更新统计信息 |
最佳实践总结
- 少而精:控制索引数量,优先组合索引覆盖高频查询。
- 左前缀:组合索引列顺序 = 查询条件顺序 + 区分度降序。
- 覆盖查询:让索引包含
SELECT
所需字段,消除回表。 - Online DDL:大表操作使用
ALGORITHM=INPLACE, LOCK=NONE
。 - 持续监控:定期检查索引使用率,删除无用索引。
通过精准设计索引,可让查询速度提升十倍甚至百倍,但需时刻权衡读写性能。永远基于真实查询负载和数据分布做决策,避免盲目添加索引。