1. MySQL索引失效的典型场景剖析
索引失效是数据库性能优化中最常见也最容易被忽视的问题。作为从业15年的DBA,我处理过上千起性能故障案例,其中约60%都与不当的索引使用有关。以下是30种高频索引失效场景,按失效机制分为6大类:
1.1 隐式类型转换陷阱
当查询条件的数据类型与索引列定义不一致时,MySQL会进行隐式类型转换,导致索引失效。常见于:
sql复制-- 案例1:字符串与数字比较
-- user_id字段为varchar但存储数字值
EXPLAIN SELECT * FROM users WHERE user_id = 10086;
-- 实际执行类型转换:CAST(user_id AS SIGNED) = 10086
-- 案例2:日期格式不匹配
-- create_time为DATETIME类型
EXPLAIN SELECT * FROM orders WHERE create_time = '2023-05-01';
-- 应使用完整格式:'2023-05-01 00:00:00'
诊断技巧:通过EXPLAIN查看type列,出现"ALL"或"index"但rows值异常高时,需检查WHERE条件类型匹配性
1.2 函数操作导致的索引失效
对索引列使用函数会使优化器无法使用索引树定位数据:
sql复制-- 案例3:日期函数截断
-- 索引:idx_create_time(create_time)
EXPLAIN SELECT * FROM logs WHERE DATE(create_time) = '2023-05-01';
-- 优化方案:改为范围查询
SELECT * FROM logs
WHERE create_time BETWEEN '2023-05-01 00:00:00' AND '2023-05-01 23:59:59';
-- 案例4:字符串函数
-- 索引:idx_username(username)
EXPLAIN SELECT * FROM users WHERE LEFT(username, 3) = 'dev';
-- 优化方案:使用前缀索引或全文索引
1.3 最左前缀原则违反
复合索引必须遵循最左匹配原则,常见错误包括:
sql复制-- 案例5:跳过引导列
-- 索引:idx_comp(a,b,c)
EXPLAIN SELECT * FROM table WHERE b = 1 AND c = 2;
-- 必须包含a列才能使用索引
-- 案例6:范围查询阻断后续列
-- 索引:idx_comp(create_time, status)
EXPLAIN SELECT * FROM orders
WHERE create_time > '2023-01-01' AND status = 1;
-- status列无法被索引利用
1.4 索引选择性不足
当索引列区分度太低时,优化器可能放弃使用索引:
sql复制-- 案例7:性别字段索引
-- gender列只有'M','F'两种值
CREATE INDEX idx_gender ON users(gender);
-- 实际查询时可能全表扫描
-- 案例8:状态字段索引
-- status列95%的值都是1
CREATE INDEX idx_status ON orders(status);
-- 查询status=1时不会走索引
经验阈值:索引选择性=不同值数量/总记录数,低于0.1时需谨慎使用
1.5 优化器误判
统计信息不准确或成本估算错误导致:
sql复制-- 案例9:小表全表扫描
-- 表中只有10条记录时
EXPLAIN SELECT * FROM config WHERE key_name = 'timeout';
-- 优化器认为全表扫描更快
-- 案例10:索引合并失效
-- 存在idx_a(a)和idx_b(b)
EXPLAIN SELECT * FROM table WHERE a = 1 OR b = 2;
-- 早期版本可能不使用index_merge
1.6 特殊语法问题
sql复制-- 案例11:IS NULL判断
-- 索引:idx_name(name)
EXPLAIN SELECT * FROM users WHERE name IS NULL;
-- 需确认NULL值比例,高版本支持NULL索引
-- 案例12:不等于操作
-- 索引:idx_status(status)
EXPLAIN SELECT * FROM orders WHERE status != 1;
-- 需配合其他条件使用
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 索引失效的深度诊断技术
2.1 EXPLAIN执行计划解析
关键字段解读:
- type:从优到差 system > const > eq_ref > ref > range > index > ALL
- possible_keys:可能使用的索引
- key:实际使用的索引
- rows:预估检查行数
- Extra:Using filesort/Using temporary表示性能瓶颈
2.2 性能模式监控
sql复制-- 开启性能监控
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME LIKE '%events_statements%';
-- 查看高成本SQL
SELECT * FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
2.3 索引使用统计
sql复制-- 查看索引使用频率
SELECT object_schema, object_name, index_name,
count_star, count_read, count_fetch
FROM performance_schema.table_io_waits_summary_by_index_usage
ORDER BY count_star DESC;
3. 索引优化实战方案
3.1 索引设计黄金法则
- 单表索引不超过5个
- 复合索引列数不超过3列
- 区分度高的列在前
- 等值查询列优先于范围查询列
- 避免冗余索引
3.2 索引优化模板
sql复制-- 优化前
SELECT * FROM orders
WHERE YEAR(create_time) = 2023
AND status = 1
ORDER BY amount DESC;
-- 优化后方案
ALTER TABLE orders
ADD INDEX idx_status_createtime_amount(status, create_time, amount);
SELECT * FROM orders
WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'
AND status = 1
ORDER BY amount DESC;
3.3 强制索引使用技巧
sql复制-- 强制使用特定索引
SELECT * FROM orders FORCE INDEX(idx_status)
WHERE status = 1;
-- 忽略索引
SELECT * FROM orders IGNORE INDEX(idx_status)
WHERE status = 1 AND create_time > '2023-01-01';
4. 高级索引失效场景
4.1 分区表索引陷阱
sql复制-- 案例13:跨分区查询
-- 按range分区且未包含分区键
EXPLAIN SELECT * FROM sales
WHERE product_id = 100
AND sale_date BETWEEN '2023-01-01' AND '2023-03-31';
-- 需确保分区键在查询条件中
4.2 字符集与排序规则
sql复制-- 案例14:不同字符集比较
-- utf8mb4与utf8列关联
EXPLAIN SELECT * FROM t1 JOIN t2
ON t1.name = t2.name
WHERE t1.charset = 'utf8mb4'
AND t2.charset = 'utf8';
-- 需统一字符集
4.3 子查询优化
sql复制-- 案例15:IN子查询
EXPLAIN SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);
-- 可改写为JOIN
-- 案例16:EXISTS优化
EXPLAIN SELECT * FROM products p
WHERE EXISTS (
SELECT 1 FROM inventory i
WHERE i.product_id = p.id AND i.quantity > 0
);
-- 确保关联字段有索引
5. 索引维护与监控
5.1 索引碎片整理
sql复制-- 查看碎片率
SELECT table_name, index_name,
ROUND(data_free/(data_length+index_length)*100,2) AS frag_ratio
FROM information_schema.tables
WHERE table_schema = 'your_db'
AND data_free > 0;
-- 重建索引
ALTER TABLE orders ENGINE=InnoDB;
-- 或
OPTIMIZE TABLE orders;
5.2 索引使用监控
sql复制-- 长期未使用索引查询
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = 'your_db';
-- 索引使用效率统计
SELECT * FROM sys.schema_index_statistics
WHERE table_schema = 'your_db';
6. 特殊场景解决方案
6.1 JSON字段索引
sql复制-- 案例17:JSON路径查询
ALTER TABLE products
ADD INDEX idx_category((CAST(properties->'$.category' AS CHAR(10))));
EXPLAIN SELECT * FROM products
WHERE properties->'$.category' = 'electronics';
6.2 全文索引优化
sql复制-- 案例18:模糊查询优化
ALTER TABLE articles
ADD FULLTEXT INDEX idx_content(content);
-- 自然语言模式
EXPLAIN SELECT * FROM articles
WHERE MATCH(content) AGAINST('数据库优化' IN NATURAL LANGUAGE MODE);
-- 布尔模式
EXPLAIN SELECT * FROM articles
WHERE MATCH(content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);
6.3 空间数据索引
sql复制-- 案例19:地理位置查询
ALTER TABLE stores
ADD SPATIAL INDEX idx_location(location);
EXPLAIN SELECT * FROM stores
WHERE ST_Distance_Sphere(location, POINT(116.404, 39.915)) < 1000;
7. 版本特性差异
7.1 MySQL 5.7优化
sql复制-- 案例20:生成列索引
ALTER TABLE products
ADD COLUMN price_tax DECIMAL(10,2) AS (price*1.1) STORED,
ADD INDEX idx_price_tax(price_tax);
7.2 MySQL 8.0新特性
sql复制-- 案例21:降序索引
ALTER TABLE orders
ADD INDEX idx_create_time_desc(create_time DESC);
-- 案例22:函数索引
CREATE INDEX idx_name_lower ON users((LOWER(username)));
8. 索引设计反模式
8.1 过度索引问题
sql复制-- 案例23:重复索引
-- 已有idx_a_b(a,b)又创建idx_a(a)
-- 案例24:无效组合
-- 索引:idx_status_create_time(status, create_time)
-- 但查询总是单独使用status
8.2 索引选择失误
sql复制-- 案例25:过长的字符串索引
-- 对TEXT列建前缀索引但长度不足
ALTER TABLE articles
ADD INDEX idx_content(content(10));
-- 实际需要前50字符才能区分
-- 案例26:枚举值索引
-- 对ENUM列建索引但值很少变化
9. 生产环境案例复盘
9.1 电商订单查询优化
原始场景:
sql复制SELECT * FROM orders
WHERE user_id = 12345
AND order_status IN (2,3,5)
AND create_time BETWEEN '2023-01-01' AND '2023-06-30'
ORDER BY update_time DESC;
问题诊断:
- 存在idx_user(user_id)和idx_status_time(order_status, create_time)
- 优化器选择使用idx_user但需要回表过滤其他条件
- 排序字段未在索引中导致filesort
优化方案:
sql复制ALTER TABLE orders
ADD INDEX idx_user_status_time(user_id, order_status, create_time, update_time);
-- 改写查询确保最左匹配
EXPLAIN SELECT * FROM orders
WHERE user_id = 12345
AND order_status IN (2,3,5)
AND create_time >= '2023-01-01'
AND create_time <= '2023-06-30'
ORDER BY update_time DESC;
9.2 社交平台Feed流优化
挑战:
- 千万级数据量的用户动态表
- 需要按时间倒序展示好友动态
- 常见查询:WHERE user_id IN (好友列表) ORDER BY create_time DESC
解决方案:
- 使用复合索引(user_id, create_time)
- 对IN列表进行预排序使其与索引顺序一致
- 使用延迟关联减少回表:
sql复制SELECT t.* FROM (
SELECT id FROM feeds
WHERE user_id IN (1,5,9)
ORDER BY create_time DESC
LIMIT 100
) tmp JOIN feeds t ON tmp.id = t.id;
10. 索引优化检查清单
10.1 设计阶段检查项
- [ ] 是否所有高频查询条件都有合适索引
- [ ] 复合索引的列顺序是否符合最左前缀原则
- [ ] 索引选择性是否足够高(>0.1)
- [ ] 是否避免了重复/冗余索引
- [ ] 排序和分组字段是否包含在索引中
10.2 运维阶段检查项
- [ ] 定期检查未使用索引
- [ ] 监控索引碎片率(>30%需整理)
- [ ] 统计信息是否及时更新
- [ ] 检查版本特性是否可用新索引类型
- [ ] 压力测试验证索引效果
终极建议:任何索引变更都应先在测试环境验证,通过EXPLAIN和实际查询性能双重确认效果
