1. 锁机制
1.1 锁的作用
-
保证数据一致性,防止并发操作导致的数据冲突
-
控制并发操作顺序
-
提高性能(合理使用锁比死锁和冲突少)
1.2 锁的分类
按作用对象
| 类型 | 作用对象 | 特点 |
|---|---|---|
| 表锁 | 整张表 | 简单、开销低,但并发能力低 |
| 行锁 | 单条记录 | 并发能力高,但开销大,需要索引支持 |
按锁类型
| 类型 | 描述 | SQL 示例 |
|---|---|---|
| 共享锁(S) | 允许读,不允许写 | SELECT ... LOCK IN SHARE MODE |
| 排他锁(X) | 独占读写 | UPDATE ... 或 SELECT ... FOR UPDATE |
按使用方式
-
显式锁 :用户手动加锁 (
SELECT ... FOR UPDATE) -
隐式锁:InnoDB 自动加锁(UPDATE/DELETE 自动加 X 锁)
1.3 InnoDB 行锁机制
| 锁类型 | 锁对象 | 作用 | 场景 |
|---|---|---|---|
| 记录锁(Record Lock) | 单条索引记录 | 防止并发修改同一行 | UPDATE / SELECT ... FOR UPDATE |
| 间隙锁(Gap Lock) | 索引记录间的空隙 | 防止幻读(插入新行) | 范围查询 + 更新 |
| 临键锁(Next-Key Lock) | 记录 + 前后空隙 | 防止幻读 + 并发修改 | InnoDB 默认 REPEATABLE READ 范围查询 |
例子:
SELECT * FROM users WHERE id BETWEEN 2 AND 4 FOR UPDATE;
锁住 id=3 记录 + id=2~4 的间隙
1.4 幻读(Phantom Read)
-
定义:同一事务内相同条件查询两次,第二次出现新增/消失行
-
出现原因:事务 A 查询条件范围内,事务 B 插入新行
-
防止方法 :InnoDB 使用 临键锁(记录锁 + 间隙锁)
-
示例:
-- 事务A SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE; -- 事务B INSERT INTO users(id, age) VALUES(4, 22); -- 阻塞
1.5 事务隔离级别与锁关系
| 隔离级别 | 锁行为 | 可见现象 |
|---|---|---|
| READ UNCOMMITTED | 不加锁 | 脏读 |
| READ COMMITTED | 写加 X 锁,读不加锁 | 无脏读,可不可重复读 |
| REPEATABLE READ | 快照读 + 写加 X 锁 | 防脏读、不可重复读,幻读通过行锁/间隙锁避免 |
| SERIALIZABLE | 读加 S 锁,写加 X 锁 | 最严格,顺序执行,性能低 |
1.6 死锁与优化
死锁产生:
- 两个事务互相等待对方持有的锁
优化技巧:
-
统一加锁顺序
-
缩短事务执行时间
-
使用索引加行锁,避免全表扫描
-
使用低隔离级别读(READ COMMITTED)
2. MVCC(多版本并发控制)
2.1 概念
事务读取自己的快照,读不阻塞写,写不阻塞读,提高并发性能
2.2 原理
-
每行记录有隐藏字段:
trx_id、roll_pointer -
事务只读取自己快照的版本
-
写操作生成新版本,旧版本存在 undo log
2.3 支持隔离级别
| 隔离级别 | MVCC 行为 |
|---|---|
| READ COMMITTED | 每次查询读取最新提交版本 |
| REPEATABLE READ | 事务启动时快照,多次查询读取同一版本 |
| 幻读 | 临键锁防止幻读 |
2.4 示例
-- 事务A START TRANSACTION; SELECT * FROM users WHERE age > 20; -- 事务B UPDATE users SET age = 30 WHERE id = 1; COMMIT; -- 事务A再次查询仍看到旧版本
3. B+ 树与索引
3.1 B+ 树概念
-
平衡树,所有叶子节点在同一层
-
内部节点只存索引,叶子节点存数据
-
叶子节点链表连接,支持范围查询
3.2 MySQL 为什么用 B+ 树
-
查找高效:O(log n),树高低,磁盘 I/O 少
-
范围查询快:叶子链表顺序扫描
-
排序快 :叶子节点有序支持
ORDER BY -
插入删除稳定:节点分裂/合并保持平衡
-
磁盘友好:内部节点存索引,可批量读入
3.3 聚簇索引 vs 辅助索引
-
聚簇索引(Primary Key):叶子节点存完整行
-
辅助索引(Secondary Key):叶子节点存主键,通过主键查聚簇索引
3.4 深分页问题
-
OFFSET 深分页需要遍历大量叶子节点链表
-
解决方案:
-
Keyset 分页(
WHERE id > 上一页最后 id) -
分区/分表
-
缓存或搜索引擎
-
4. 总结
| 知识点 | 核心理解 |
|---|---|
| 锁机制 | 表锁/行锁、共享锁/排他锁、临键锁防幻读 |
| 幻读 | 范围查询被其他事务插入新行导致结果变化 |
| MVCC | 多版本控制,提高并发,读不阻塞写 |
| B+ 树 | 高效查找、范围查询、排序,叶子节点链表支持顺序扫描 |
| 深分页优化 | Keyset 分页 + 分区/分表 + 缓存 |
💡 一句话面试总结:
MySQL 通过 B+ 树索引、MVCC、多种行锁和临键锁,实现高并发、高效查找和范围查询,同时防止脏读、不可重复读和幻读,深分页需要 Keyset 优化。