2.1 慢查询定位与 EXPLAIN
如何定位慢查询
| 方案 | 工具 |
|---|---|
| 开源工具 | Arthas(调试)、Prometheus / Skywalking(运维监控) |
| MySQL 自带 | 慢日志(slow query log) |
EXPLAIN 分析 SQL
使用 MySQL 自带的 EXPLAIN 分析执行计划。
Type 类型(性能由好到差):
NULL → system → const → eq_ref → ref → range → index → ALL
2.2 索引
索引结构比较
二叉树 / 红黑树的缺点:树的高度太高,导致磁盘 IO 次数多,查询效率低。
B 树:节点中有指针指向不同数据范围,提高检索效率。
B+ 树:将具体数据都放在叶子节点上,非叶子节点只存索引,进一步提高了效率。
聚簇索引 vs 非聚簇索引 vs 回表
| 概念 | 类比 | 说明 |
|---|---|---|
| 聚簇索引 | 按拼音排序的字典正文 | 索引和数据是一体的,找到索引 = 找到数据 |
| 非聚簇索引 | 偏旁部首检字表 | 索引只告诉你”数据在哪”,还需回表查数据 |
回表:通过二级索引找到主键 → 再通过主键去聚簇索引查完整数据。
覆盖索引 / 超大分页优化
覆盖索引:查询需要的列全部包含在索引中,无需回表。
超大分页优化思路:利用子查询 + 覆盖索引查找主键 id,再关联回表,避免原始 LIMIT 造成的逐行回表。
索引创建原则
- 数据量较大、查询频繁的表建立索引
- 针对常作为查询条件、排序、分组的字段
- 尽量使用区分度高的列
- 长字符串字段可建立前缀索引
- 尽量使用联合索引,减少单列索引,利用覆盖索引避免回表
- 控制索引数量,维护成本随索引数增长
- 索引列不能存 NULL,用
NOT NULL约束,帮助优化器选择
索引失效场景
| 场景 | 原因 |
|---|---|
| 违反最左前缀法则 | 联合索引不按顺序使用 |
| 范围查询右边的列 | 范围查询会中断索引使用 |
| 索引列上进行运算 | 索引失效 |
| 字符串不加单引号 | 隐式类型转换导致失效 |
LIKE '%xxx' 以 % 开头 | 前缀模糊无法使用索引 |
2.3 SQL 优化经验
表设计优化(参考阿里开发手册)
- 设置合适的数值类型,选择合适的字段长度
CHAR定长效率高,VARCHAR可变长效率稍低
SQL 语句优化
SELECT指明字段名,避免SELECT *- 避免索引失效的写法
- 尽量用
UNION ALL代替UNION(UNION 多一次去重操作) - 避免在
WHERE子句中对字段进行表达式操作 - JOIN 优化:能用
INNER JOIN就不用LEFT/RIGHT JOIN。必须使用时,小表作为驱动表。内连接会自动优化将小表放外部;外连接不会调整顺序。
2.4 MySQL 事务
事务特性(ACID)
- A(原子性):事务是不可分割的最小操作单元
- C(一致性):事务前后数据保持一致
- I(隔离性):事务之间互不干扰
- D(持久性):事务提交后数据永久保存
ACID 在 InnoDB 里怎么实现
一句话:A 靠 Undo Log,D 靠 Redo Log,I 靠 MVCC + 锁;C 是目标,不是单独某一个日志。
| 特性 | 核心机制 | 解决的问题 |
|---|---|---|
| A 原子性 | Undo Log | 回滚到事务开始前的状态 |
| D 持久性 | Redo Log(WAL) | 崩溃后恢复已提交的修改 |
| I 隔离性 | MVCC + 锁 | 读不阻塞写、写不阻塞读;写写排队 |
| C 一致性 | A/D/I + 约束 + 业务逻辑 | 数据从一个合法状态到另一个合法状态 |
A:Undo Log(回滚日志)
- 修改数据之前,先把旧值/反向操作记录到 Undo Log;
- 事务执行
ROLLBACK时,按 Undo Log 逆向还原; - Undo Log 同时是 MVCC 版本链的一部分,事务提交后由 purge 线程清理。
D:Redo Log(重做日志)
- 修改先发生在 Buffer Pool,同时记录 Redo Log;
- 提交时按
innodb_flush_log_at_trx_commit决定是否 fsync 到磁盘; - Redo Log 是顺序写,比随机写表文件快,这就是 WAL(Write-Ahead Logging)的意义;
- 崩溃恢复时重放 Redo Log,把「已提交但还没来得及刷回表文件」的修改补上;
innodb_flush_log_at_trx_commit=1(默认)最安全,=2提交只写 OS cache,=0每秒刷一次;- 生产常说的「双 1」:
innodb_flush_log_at_trx_commit=1+sync_binlog=1。
⚠️ Redo Log 保证的是「已提交事务的修改不丢」,不是「数据已经立刻写进表文件」。表文件由后台线程异步刷盘。
I:MVCC + 锁
- InnoDB 行里有隐藏列
DB_TRX_ID(最后修改它的事务 ID)和DB_ROLL_PTR(指向 Undo Log 的版本链); - 普通
SELECT走 MVCC:根据 ReadView 判断哪个版本可见,实现「读不阻塞写、写不阻塞读」; - RC(读已提交):每条语句生成一个 ReadView;
- RR(可重复读,默认):事务第一次读时生成 ReadView,之后复用,所以同一事务内读到的是同一个快照;
- 写写冲突靠行锁排队;RR 下当前读还会用 next-key lock(记录锁 + 间隙锁)处理幻读。
C:一致性
- A/D/I 保证数据库层面的正确性;
- 还需要主键、唯一、非空、外键、
CHECK等约束; - 业务逻辑本身必须正确——数据库不能替你保证「不能扣错账户」这种语义。
Undo / Redo / Binlog 的区别
| 日志 | 层次 | 作用 | 能回滚吗 |
|---|---|---|---|
| Undo Log | InnoDB | 回滚 + MVCC 版本链 | ✅ |
| Redo Log | InnoDB | 崩溃恢复、保证持久性 | ❌ |
| Binlog | MySQL Server | 主从复制、归档、数据恢复 | ❌(逻辑日志) |
并发事务带来的问题
- 脏读:读到其他事务未提交的数据
- 不可重复读:同一事务内两次读取结果不同
- 幻读:同一事务内两次查询结果集不同
事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ❌ | ❌ | ❌ |
| READ COMMITTED | ✅ | ❌ | ❌ |
| REPEATABLE READ(默认) | ✅ | ✅ | ❌(部分解决) |
| SERIALIZABLE | ✅ | ✅ | ✅ |