🌟 MySQL索引优化:从入门到实战的保姆级指南
🔥 开篇必读:为什么你的索引总是无效?
真实案例:某电商平台订单查询从12秒优化到0.3秒的惊险历程
某程序员给order_no
字段添加了索引,但查询依然卡顿。
问题根源:order_no
是varchar(64)
且包含时间前缀,索引长度高达20字节。
解决方案:改用bigint
自增主键 + 二级索引,性能提升40倍!
本文学完你将掌握:
✅ 索引底层B+树的工作原理(动画演示)
✅ EXPLAIN执行计划的20个关键细节
✅ 6大真实场景的优化方案(附可运行SQL)
📌 一、B+树索引的底层机密(图解版)
1.1 为什么索引字段越小越好?
原理剖析:
- 每个B+树节点默认16KB,可存储更多小字段
- 对比实验:
-- 方案1:使用varchar(255)做主键
CREATE TABLE t1 (id varchar(255) PRIMARY KEY, ...);
-- 方案2:使用bigint做主键
CREATE TABLE t2 (id bigint PRIMARY KEY, ...);
性能差距:百万数据量下查询速度相差5倍!
1.2 最左前缀原则的5大误区
误区1:联合索引顺序不影响查询
-- 索引 (a,b,c)
WHERE b=1 AND a=2 -- ✅ 命中
WHERE c=1 AND b=2 -- ❌ 未命中
误区2:范围查询后所有列失效
-- 索引 (age,score,city)
WHERE age>18 AND score=90 -- ✅ score仍可用
误区3:like模糊查询必定失效
-- 索引 (name)
WHERE name LIKE '张%' -- ✅ 命中
WHERE name LIKE '%张' -- ❌ 失效
💣 二、索引失效的12种高危场景(附解决方案)
2.1 隐式类型转换
-- user_id是varchar类型
SELECT * FROM users WHERE user_id = 10086; -- ❌ 触发CAST转换
修复方案:
SELECT * FROM users WHERE user_id = '10086'; -- ✅
2.2 对索引列使用函数
-- create_time是datetime类型
SELECT * FROM logs WHERE DATE(create_time) = '2023-07-15'; -- ❌
优化方案:
SELECT * FROM logs
WHERE create_time BETWEEN '2023-07-15 00:00:00' AND '2023-07-15 23:59:59'; -- ✅
2.3 OR条件短路
-- 索引 (age)
SELECT * FROM students WHERE age=18 OR name='张三'; -- ❌
优化方案:
SELECT * FROM students WHERE age=18
UNION
SELECT * FROM students WHERE name='张三'; -- ✅
完整失效清单:
- 使用
!=
或<>
操作符 - 联合索引跳过最左列
- 对索引列进行运算
- 使用NOT EXISTS子查询
- 不同字符集比较
- 使用%开头的LIKE查询
- 字段类型不匹配
- OR连接非索引列
- 使用全表扫描提示
- 索引列参与表达式
- 使用NOT IN条件
- 低选择性索引(如性别字段)
🚀 三、性能翻倍的索引设计模板
3.1 三星索引设计法(电商案例)
场景:订单表高频查询
SELECT order_id, total_price, status
FROM orders
WHERE user_id=123
AND create_time > '2023-01-01'
ORDER BY create_time DESC
LIMIT 10;
索引设计:
ALTER TABLE orders ADD INDEX idx_usr_time_status (user_id, create_time DESC, status);
三星达成:
- WHERE条件⭐:user_id和create_time精准匹配
- ORDER BY⭐:create_time排序消除filesort
- SELECT⭐:覆盖所有查询字段
3.2 覆盖索引优化技巧
普通查询:
EXPLAIN SELECT * FROM products WHERE category='手机';
-- type: ref, Extra: Using index condition
覆盖索引优化:
ALTER TABLE products ADD INDEX idx_cat (category, name, price);
EXPLAIN SELECT name, price FROM products WHERE category='手机';
-- type: ref, Extra: Using index
性能对比:
查询类型 | 执行时间 | 扫描行数 |
---|---|---|
回表查询 | 8.2ms | 10,326 |
覆盖索引 | 0.9ms | 10,326 |
🛠️ 四、实战:500万数据的分页优化
4.1 传统分页的致命缺陷
SELECT * FROM user_logs
ORDER BY create_time DESC
LIMIT 4800000, 20; -- 执行时间12秒
4.2 游标分页法
SELECT * FROM user_logs
WHERE create_time < '2023-07-01 00:00:00'
ORDER BY create_time DESC
LIMIT 20; -- 执行时间0.2秒
4.3 延迟关联法
SELECT t1.* FROM user_logs t1
JOIN (
SELECT id FROM user_logs
ORDER BY create_time DESC
LIMIT 4800000, 20
) t2 ON t1.id = t2.id; -- 执行时间1.8秒
🔍 五、索引健康检查清单(每日必做)
- 检查冗余索引
SELECT * FROM sys.schema_redundant_indexes;
- 监控未使用索引
SELECT * FROM sys.schema_unused_indexes;
- 索引统计信息更新
ANALYZE TABLE orders;
工具推荐:
- pt-index-usage:分析慢查询日志
- pt-duplicate-key-checker:查找重复索引
💬 互动:你的索踩过哪些坑?
评论区高频问题精选:
Q:UUID做主键真的性能很差吗?
A:实测v4 UUID比自增ID插入速度慢3倍,存储空间多20字节
Q:全文索引和like模糊查询哪个快?
A:10万数据量下,MATCH…AGAINST比LIKE快15倍