一、索引机制:B+树是爹,但爹也会坑你!
1. 索引底层:B+树的那些骚操作
- 叶子链表:范围查询直接按链表扫,但如果你他妈乱用
LIKE '%xxx'
,B+树直接罢工(无法利用索引)。 - 页分裂与碎片:频繁插入无序数据(比如UUID主键),页分裂成狗,性能暴跌。
- 优化:用自增主键,让插入集中在B+树右侧,减少分裂。
- 监控:
SHOW STATUS LIKE '%innodb_page_splits%'
,数值高说明该优化了。
2. 覆盖索引:少一次IO,多一条活路
- 原理:二级索引的叶子节点直接包含查询列,避免回表(少一次B+树查找)。
- 实战:
“覆盖索引就是让快递小哥直接把包裹塞你手里,别TM再跑一趟!”-- 烂查询:回表查name SELECT name FROM users WHERE age = 25; -- 优化:加联合索引 (age, name) ALTER TABLE users ADD INDEX idx_age_name(age, name);
3. 最左前缀原则:联合索引的潜规则
- 失效场景:
- 查询跳过最左列 → 索引失效。
- 对索引列用函数 → 索引失效(如
WHERE YEAR(create_time) = 2023
)。
- 优化:
- 把高频查询条件放最左:比如
(user_id, status)
,如果总查WHERE user_id=xxx AND status=1
。 - 虚拟列黑科技:MySQL 5.7+支持生成列,绕过函数索引限制。
ALTER TABLE orders ADD COLUMN create_year YEAR AS (YEAR(create_time)) VIRTUAL; ALTER TABLE orders ADD INDEX idx_create_year(create_year);
- 把高频查询条件放最左:比如
二、事务与锁:并发控制的修罗场
1. 事务隔离级别:你越严格,性能越拉胯
- 默认
REPEATABLE READ
:用MVCC+间隙锁防幻读,但写操作可能被锁成狗。 - 优化建议:
- 读多写少场景 → 用
READ COMMITTED
,减少间隙锁竞争。 - 短事务为王 → “长事务不commit,锁等超时让你哭!”
- 读多写少场景 → 用
2. 锁机制:行锁、间隙锁、临键锁
- 行锁:锁具体一行,
UPDATE
时自动加锁。 - 间隙锁:锁区间,防幻读(比如
WHERE id > 100
,锁住100到正无穷的空隙)。 - 死锁现场:
“死锁就像两辆车在胡同里顶牛,谁也别想过!”-- 事务1 UPDATE users SET score=100 WHERE id=1; UPDATE users SET score=200 WHERE id=2; -- 事务2 UPDATE users SET score=200 WHERE id=2; UPDATE users SET score=100 WHERE id=1; -- 互相等锁 → 死锁!
- 优化:
- 统一SQL执行顺序(比如按ID升序操作)。
- 用
SHOW ENGINE INNODB STATUS
查死锁日志,调整业务逻辑。
3. MVCC:多版本控制的障眼法
- 原理:每条数据存多个版本,通过
undo log
链实现“读旧版本”。 - 快照读 vs 当前读:
SELECT ...
默认快照读(无锁,读旧版本)。SELECT ... FOR UPDATE
用当前读(加锁,读最新版本)。
- 优化:
- 写后立即读的场景 → 用
FOR UPDATE
避免读到旧数据。 - 读多写少场景 → 快照读性能起飞。
- 写后立即读的场景 → 用
三、SQL优化:从青铜到王者的骚操作
1. 避免全表扫描:索引是爹,但别瞎认爹
EXPLAIN
命令:看执行计划,type=ALL
就是全表扫描,赶紧加索引!- 隐式类型转换:
-- user_id是VARCHAR类型,但用了数字 → 索引失效! SELECT * FROM users WHERE user_id = 10086; -- 优化:改成字符串 SELECT * FROM users WHERE user_id = '10086';
2. 分页查询:别用OFFSET
,会死!
LIMIT 100000, 10
:MySQL先读100010行,再丢100000行,IO爆炸。- 优化:
“OFFSET分页就像让你从第100页开始读,但必须先翻完前99页!”-- 用id游标分页 SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10;
3. JOIN优化:小表驱动大表
- 原则:用
小表 JOIN 大表
,减少循环次数。 - STRAIGHT_JOIN强制驱动表:
-- 强制用users表驱动orders表 SELECT /*+ STRAIGHT_JOIN */ * FROM users JOIN orders ON users.id = orders.user_id;
- 索引覆盖JOIN列:确保
ON
和WHERE
的字段有索引。
4. 冷热数据分离:拆表大法好
- 场景:订单表历史数据很少查,但体积占90%。
- 优化:
- 按时间分区:
PARTITION BY RANGE (YEAR(create_time))
。 - 拆成
orders_active
和orders_archive
,定期迁移数据。
- 按时间分区:
四、参数调优:让InnoDB榨干硬件性能
1. 内存分配:Buffer Pool是命根子
innodb_buffer_pool_size
:设成机器内存的70%~80%,缓存数据和索引。- 监控命中率:
SHOW STATUS LIKE 'Innodb_buffer_pool_read%'; -- 命中率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) -- 低于99%?赶紧加内存!
2. 日志优化:Redo Log和Binlog的平衡术
innodb_flush_log_at_trx_commit=1
+sync_binlog=1
:安全但性能差。- 妥协方案:
innodb_flush_log_at_trx_commit=2
+sync_binlog=1000
,牺牲一点安全性换性能。- 用带电池的RAID卡或SSD,降低刷盘延迟。
3. 线程池与并发控制
innodb_thread_concurrency
:默认0(无限制),高并发时设成CPU核数×2。- **
innodb_read_io_threads
**和innodb_write_io_threads
:SSD建议设成8~16。
五、终极总结:InnoDB优化军规
场景 | 优化口诀 |
---|---|
索引设计 | 最左前缀要遵守,覆盖索引是爸爸 |
事务与锁 | 短事务快如狗,长事务死成狗 |
SQL写法 | 避免SELECT *,分页用游标,JOIN小表驱动 |
参数调优 | Buffer Pool喂饱,Redo/Binlog别乱搞 |
架构设计 | 冷热数据拆表,大字段单独存,分区治百病 |
最后暴论:
- “不会用
EXPLAIN
的DBA,和咸鱼有什么区别?” - “索引建太多?你当MySQL是垃圾桶吗!”
- “死锁不可怕,可怕的是你连日志都不会查!”
现在,你可以去把那些慢查询按在地上摩擦了!