1. MySQL索引失效的11种典型场景深度解析
作为数据库性能优化的核心机制,索引失效问题一直是MySQL DBA和开发者的痛点。根据我多年处理生产环境性能问题的经验,索引失效往往源于对B+树底层机制和优化器工作原理的认知不足。下面我将结合具体案例,剖析11种最常见的索引失效场景。
1.1 违反最左前缀原则
联合索引(sname, s_code, address)在以下查询中表现差异巨大:
sql复制-- 有效案例(走索引)
SELECT * FROM students WHERE sname = '张三' AND s_code = 101;
-- 失效案例(全表扫描)
SELECT * FROM students WHERE s_code = 101;
原理说明:B+树的索引键值是按声明顺序排序的。就像电话簿按"姓-名"排序时,单独查"名"就无法利用排序优势。
1.2 索引列参与计算
sql复制-- 失效案例(全表扫描)
SELECT * FROM account WHERE amount + 100 > 500;
-- 优化方案(走索引)
SELECT * FROM account WHERE amount > 400;
我在金融系统曾遇到一个典型案例:对账程序每天凌晨卡死,最终发现是WHERE YEAR(create_time)=2023导致索引失效,改为范围查询后耗时从2小时降至3分钟。
1.3 隐式类型转换
sql复制-- 失效案例(varchar列比较数字)
SELECT * FROM users WHERE id_no = 123456;
-- 有效案例
SELECT * FROM users WHERE id_no = '123456';
这种问题在日志分析系统中尤为常见。建议所有字符类型比较时显式加上引号,数字类型则保持裸值。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 索引失效的优化器决策机制
2.1 回表成本过高
当预估需要回表的记录超过总行数20%-30%时,优化器可能选择全表扫描。通过以下案例说明:
sql复制-- 可能失效的场景
SELECT * FROM orders WHERE status = 'pending';
-- 优化方案
SELECT id, order_no FROM orders WHERE status = 'pending';
我曾优化过一个电商系统,将SELECT *改为只查询索引覆盖字段后,QPS从200提升到1200。
2.2 OR条件导致失效
sql复制-- 失效案例
SELECT * FROM products WHERE category_id = 5 OR price > 100;
-- 优化方案
SELECT * FROM products WHERE category_id = 5
UNION ALL
SELECT * FROM products WHERE price > 100;
在CMS系统内容检索中,这种改写通常能带来5-8倍的性能提升。但要注意UNION ALL会返回重复记录。
3. 函数操作导致的索引失效
3.1 使用内置函数
sql复制-- 失效案例
SELECT * FROM logs WHERE DATE(create_time) = '2023-01-01';
-- 优化方案
SELECT * FROM logs
WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59';
时间函数是索引失效的重灾区。在数据仓库项目中,这类问题会导致夜间批处理作业严重超时。
3.2 LIKE模糊查询
sql复制-- 失效案例(前导通配符)
SELECT * FROM articles WHERE title LIKE '%数据库%';
-- 有效案例
SELECT * FROM articles WHERE title LIKE '数据库%';
对于全文搜索需求,建议使用ES专业搜索引擎。我曾将某知识库系统的LIKE查询迁移到ES后,搜索延迟从2s降到200ms。
4. 索引失效的排查与验证
4.1 EXPLAIN执行计划分析
重点关注以下字段:
- type:ALL表示全表扫描
- key:NULL表示未使用索引
- Extra:Using filesort表示需要额外排序
sql复制EXPLAIN SELECT * FROM employees WHERE last_name LIKE '%son';
4.2 索引选择性评估
通过以下SQL计算索引的选择性:
sql复制SELECT
COUNT(DISTINCT status)/COUNT(*) AS selectivity
FROM orders;
经验值:选择性低于0.1的索引往往效果不佳。在用户画像系统中,性别字段建索引就是典型的反面案例。
5. 索引使用的最佳实践
5.1 联合索引设计原则
设计口诀:"高频查询在前,高选择性在前,等值查询在前"。例如用户表查询场景:
sql复制-- 查询模式
WHERE region = '华东' AND age > 25 ORDER BY create_time DESC
-- 推荐索引
ALTER TABLE users ADD INDEX idx_region_age_ctime(region, age, create_time);
5.2 覆盖索引优化
通过包含所有查询字段避免回表:
sql复制-- 原始查询
SELECT id, name, age FROM students WHERE class_id = 3;
-- 优化索引
ALTER TABLE students ADD INDEX idx_class_cover(class_id, name, age);
在报表系统中,这种优化曾将查询速度提升10倍以上。
6. 特殊场景下的索引失效
6.1 IS NULL/IS NOT NULL
sql复制-- 可能失效(取决于数据分布)
SELECT * FROM customers WHERE mobile IS NULL;
解决方案:对NULL值较多的列考虑使用过滤索引:
sql复制CREATE INDEX idx_mobile ON customers(mobile) WHERE mobile IS NOT NULL;
6.2 不等于(!=/<>)操作
sql复制-- 失效案例
SELECT * FROM products WHERE status != 'offline';
建议改写为:
sql复制SELECT * FROM products WHERE status IN ('online', 'draft');
7. 索引维护与监控
定期检查无效索引:
sql复制SELECT * FROM sys.schema_unused_indexes;
我在某次健康检查中发现一个表有6个冗余索引,删除后写性能提升30%。
8. MySQL 8.0的索引增强特性
8.1 降序索引
sql复制-- 8.0支持降序索引
ALTER TABLE orders ADD INDEX idx_amount_desc(amount DESC);
8.2 函数索引
sql复制-- 创建函数索引
CREATE INDEX idx_name_lower ON employees((LOWER(name)));
9. 生产环境案例分析
某电商平台大促期间出现数据库CPU飙升,经分析是以下查询导致:
sql复制SELECT * FROM order_items
WHERE product_id IN (SELECT id FROM products WHERE category = 'electronics')
优化方案:
sql复制SELECT oi.* FROM order_items oi
JOIN products p ON oi.product_id = p.id
WHERE p.category = 'electronics'
优化后CPU使用率从90%降至40%。
10. 索引设计思维导图
(此处应有一个索引设计决策流程图,包含以下关键节点:
- 查询频率分析
- 字段选择性评估
- 排序/分组需求
- 数据更新频率
- 存储空间考量)
11. 索引优化检查清单
- 所有SQL都通过EXPLAIN验证了吗?
- 联合索引字段顺序是否匹配查询模式?
- 是否避免了对索引列的函数操作?
- 是否考虑了覆盖索引的可能性?
- 定期清理了未使用的索引吗?
记得在灰度环境验证索引变更效果。去年某次索引调整导致写入QPS下降50%,幸亏在预发环境及时发现。
