🚫 MySQL查询性能陷阱
动态可视化 - 深入理解索引失效的原理
1
LIKE '%keyword%' - 前置百分号陷阱
索引失效
📝 代码示例
-- ❌ 错误:前置百分号,索引失效
SELECT * FROM users WHERE name LIKE '%明%';
-- 原因:B+树无法定位起始位置
-- ✅ 正确:后置百分号,可以使用索引
SELECT * FROM users WHERE name LIKE '张%';
🎯 B+树索引查找原理
💡 原理解释
B+树索引
是有序存储的,支持前缀匹配
LIKE '张%'
:可以从"张"开始定位,然后顺序扫描
LIKE '%明%'
:无法知道起始位置,只能全表扫描
解决方案
:使用全文索引、搜索引擎(Elasticsearch)
🐌
LIKE '%明%'
全表扫描
100万行 → 扫描100万次
⚡
LIKE '张%'
索引查找
100万行 → 扫描50次
2
NOT IN - 子查询陷阱
性能+NULL陷阱
📝 代码示例
-- ❌ 错误:NOT IN 有NULL陷阱
SELECT * FROM orders WHERE user_id NOT IN (SELECT user_id FROM blacklist);
-- 如果blacklist中有NULL,整个查询返回空!
-- SELECT 1 NOT IN (2, NULL) → 返回NULL,不是TRUE
-- ✅ 正确:使用 NOT EXISTS
SELECT * FROM orders o WHERE NOT EXISTS (
SELECT 1 FROM blacklist b WHERE b.user_id = o.user_id
);
🎯 NOT IN vs NOT EXISTS 执行过程
▶️ 播放动画
🔄 重置
💡 原理解释
NULL值陷阱
:NOT IN (1, 2, NULL) 永远返回FALSE
性能问题
:子查询可能执行多次,无法优化
NOT EXISTS优势
:遇到NULL正确处理,可以使用关联索引
解决方案
:用NOT EXISTS、LEFT JOIN + IS NULL替代
3
索引列上计算 - 函数陷阱
索引失效
📝 代码示例
-- ❌ 错误:在索引列上使用函数
SELECT * FROM users WHERE YEAR(create_time) = 2024;
SELECT * FROM users WHERE age + 1 > 20;
-- 原因:索引存储的是原始值,不是计算结果
-- ✅ 正确:将计算放在等号右边
SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
SELECT * FROM users WHERE age > 19;
🎯 索引存储 vs 计算后的结果
💡 原理解释
索引存储
:B+树中存储的是列的原始值(如:2024-05-01)
函数计算
:YEAR(create_time)需要对每一行进行计算
无法使用索引
:索引树按原始值排序,不是按函数结果排序
解决方案
:将计算放在等号右边,使用范围查询
🚫
YEAR(create_time)
全表扫描
每行都要计算函数
✅
create_time >= '2024-01-01'
索引范围扫描
直接定位索引范围
4
OR 条件 - 索引合并陷阱
可能索引失效
📝 代码示例
-- ❌ 危险:OR 可能导致全表扫描
SELECT * FROM users WHERE name = '张三' OR age = 20;
-- 如果name有索引,age没有索引 → 全表扫描
-- ✅ 正确:使用 UNION 替代 OR
SELECT * FROM users WHERE name = '张三'
UNION
SELECT * FROM users WHERE age = 20;
🎯 OR vs UNION 执行计划对比
💡 原理解释
OR的问题
:需要同时满足两个条件的索引才能使用索引合并
索引合并
:MySQL可能使用index_merge,但效率低
UNION优势
:每个子查询可以使用自己的索引
最佳实践
:确保OR的每个条件都有索引,或用UNION重写
📊 性能对比总结
场景
错误写法
正确写法
性能提升
LIKE查询
'%keyword%'
'keyword%'
100x ⬆️
NOT IN
NOT IN (子查询)
NOT EXISTS
10x ⬆️
索引列计算
YEAR(column)
column >= 'value'
50x ⬆️
OR条件
WHERE a=x OR b=y
UNION
5x ⬆️