1. 索引设计的核心原则
MySQL索引本质上是一种特殊的数据结构,它能以空间换时间的方式加速查询效率。但索引并非越多越好,需要遵循几个黄金法则:
-
选择性原则:选择区分度高的列建立索引。计算方式为
COUNT(DISTINCT column)/COUNT(*),当结果大于0.2时适合建索引。例如用户表的手机号字段选择性为1,而性别字段只有0.02。 -
最左前缀原则:联合索引(a,b,c)实际相当于创建了(a)、(a,b)、(a,b,c)三个索引。查询时必须包含最左列才能命中索引,例如
WHERE b=1 AND c=2就无法使用该联合索引。 -
覆盖索引原则:当索引包含所有查询字段时,引擎可以直接从索引获取数据而无需回表。例如有索引
(user_id,name),查询SELECT name FROM users WHERE user_id=100就能利用覆盖索引。
实战经验:我曾遇到一个500万行的订单表,在
status字段(只有5种状态)上建索引反而使写入性能下降30%,这就是典型的选择性不足案例。
2. 字段类型的索引陷阱
2.1 字符串索引优化
-
对长文本字段(如VARCHAR(255))建立完整索引会占用大量空间。可采用前缀索引:
sql复制ALTER TABLE articles ADD INDEX (title(20));但要注意前缀长度需要覆盖大部分区分度,可通过计算不同长度的选择性来确定最佳值。
-
避免在JSON字段上直接建索引,MySQL 8.0+可以创建函数索引:
sql复制ALTER TABLE products ADD INDEX ((CAST(properties->>'$.price' AS DECIMAL(10,2))));
2.2 数值类型的隐式转换
当查询条件与索引列类型不匹配时会发生隐式转换,导致索引失效:
sql复制-- user_id是BIGINT类型
SELECT * FROM users WHERE user_id = '10086'; -- 字符串转数字,索引失效
2.3 时间类型的范围查询
DATETIME/TIMESTAMP字段的范围查询要注意:
sql复制-- 有效用法
SELECT * FROM logs WHERE create_time BETWEEN '2023-01-01' AND '2023-01-31';
-- 失效案例(使用了函数)
SELECT * FROM logs WHERE DATE(create_time) = '2023-01-01';
3. 联合索引设计实战
3.1 列顺序决策
联合索引的列顺序应遵循:
- 等值查询条件列优先
- 范围查询条件列在后
- 排序/分组字段放在最后
例如对于查询:
sql复制SELECT * FROM orders
WHERE user_id = 123
AND status = 'paid'
AND amount > 100
ORDER BY create_time DESC;
最优索引应该是(user_id, status, amount, create_time)。
3.2 索引合并的代价
MySQL支持index merge优化,但多个单列索引合并的性能通常不如一个联合索引。通过EXPLAIN看到type=index_merge时就应考虑重构索引。
4. 索引维护与监控
4.1 碎片整理方案
随着数据修改,索引会产生碎片,可通过以下方式维护:
sql复制-- InnoDB的在线整理(MySQL 5.7+)
ALTER TABLE orders ENGINE=InnoDB;
-- 查看碎片率
SELECT table_name, index_name,
ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) size_mb,
stat_description
FROM mysql.innodb_index_stats
WHERE stat_name = 'size' AND database_name = 'your_db';
4.2 索引使用情况监控
通过performance_schema监控索引利用率:
sql复制-- 开启监控
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
WHERE NAME LIKE 'events%statements%';
-- 查询未使用的索引
SELECT object_schema, object_name, index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
AND object_schema NOT IN ('mysql', 'performance_schema');
5. 特殊场景处理方案
5.1 热点更新问题
在高并发更新场景下,自增主键可能导致B+树右侧热点。可采用以下方案:
- 使用UUID或雪花ID分散写入
- 设置
innodb_autoinc_lock_mode=2(交错模式)
5.2 全文检索优化
对于文本搜索,相比LIKE '%keyword%',更推荐:
sql复制-- 创建全文索引
ALTER TABLE articles ADD FULLTEXT INDEX (title, content);
-- 使用布尔搜索模式
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);
6. 生产环境避坑指南
-
大表加索引的正确姿势:
sql复制-- 低峰期执行,设置超时防止锁表太久 SET SESSION lock_wait_timeout = 300; ALTER TABLE large_table ADD INDEX idx_name(name), ALGORITHM=INPLACE, LOCK=NONE; -
索引失效的隐蔽情况:
- 使用
!=、NOT IN等否定操作符 - 对索引列进行运算:
WHERE YEAR(create_time) = 2023 - 使用OR连接不同字段条件(可改写成UNION)
- 使用
-
空间索引的注意事项:
sql复制-- 创建空间索引 ALTER TABLE locations ADD SPATIAL INDEX (coordinate); -- 必须使用MBR相关函数才能命中索引 SELECT * FROM locations WHERE MBRContains(ST_GeomFromText('Polygon(...)'), coordinate); -
内存不足时的索引策略:
- 调低
innodb_buffer_pool_size后,应优先保留高频查询的索引 - 对于归档数据,可考虑删除索引并改用分区表
- 调低
通过EXPLAIN分析执行计划时,要特别注意:
type列:至少达到range级别key_len:确认是否使用了完整的索引长度Extra列:出现"Using filesort"或"Using temporary"时需要优化