MySQL问题7
MySQL中的锁类型
1. 粒度锁
-
行级锁
- 针对数据表中的某一行或多行加锁
- 并发度高,但开销较大
-
表级锁
- 针对整张表加锁
- 并发度低,但开销小、加锁快
2. 意向锁
-
意向锁
- 用于表明事务即将在某些行上加共享锁或排他锁
- 作用:快速判断表是否可以加表级锁
3. 读写锁
-
共享锁(S 锁)
- 允许事务读取数据,但不能修改
- 多个事务可同时持有同一行的共享锁
-
排他锁(X 锁)
- 允许事务对数据进行读写
- 其他事务不能再获取该数据的共享锁或排他锁
4. 元数据锁(MDL)
-
元数据锁
- 用于保护表的结构定义
- 例如:防止 DDL 与 DML 并发冲突
5. InnoDB 特有锁
-
间隙锁(Gap Lock)
- 锁定索引记录之间的“间隙”
- 防止其他事务在间隙中插入数据
-
临键锁
- 行锁 + 间隙锁的组合
- 解决可重复读隔离级别下的幻读问题
-
插入意向锁
- 表示事务准备在某个间隙插入数据
- 多个事务可以同时持有,不互相冲突
-
自增锁
- 针对自增字段的特殊锁
- 保证多事务同时插入时自增值的唯一性和有序性
MySQL事务的二阶段提交
- redo log:保证崩溃恢复(crash recovery),属于 InnoDB 层。
- binlog:保证数据一致性和主从复制,属于 MySQL Server 层。
保证这两份日志的数据一致性,MySQL 引入了 二阶段提交。
1、为什么需要二阶段提交?
只写一份日志,可能会出现以下问题:
-
只写 redo log,不写 binlog
→ 主从复制时,从库无法得到这次事务。 -
只写 binlog,不写 redo log
→ 主库宕机恢复后,数据丢失,但 binlog 里却存在这条记录,主从数据不一致。
必须保证 redo log 和 binlog 的写入结果要么同时成功,要么同时失败。
这就是 二阶段提交 的目的。
2、二阶段提交的流程
事务提交时,日志写入分为 prepare 阶段 和 commit 阶段。
-
prepare 阶段
- InnoDB 写入 redo log 的 prepare 状态,表示事务即将提交,但还没提交。
- redo log 落盘,保证即使宕机,数据也能恢复。
-
写 binlog
- MySQL Server 层写入 binlog。
- 并将 binlog 刷盘。
-
commit 阶段
- InnoDB 把 redo log 状态从 prepare 改为 commit。
- 至此,事务提交完成。
- 二阶段提交 = redo log + binlog 的双写机制。
- 作用:保证事务在 崩溃恢复 和 主从复制 中的一致性。
- 关键点:先写 redo log(prepare)→ 写 binlog → redo log(commit)。
发生死锁的解决办法
1. 死锁的检测机制
-
自动检测:
InnoDB 内部有 死锁检测机制(默认开启),当检测到死锁时,会选择 回滚一个代价较小的事务,让另一个事务继续执行。- 参数:
innodb_deadlock_detect=ON
- 参数:
-
超时等待:
如果检测关闭,则依赖锁等待超时机制。- 参数:
innodb_lock_wait_timeout
(默认 50 秒)。
- 参数:
-
kill发生死锁的语句:
2. 排查死锁的方式
-
查看最近死锁日志
SHOW ENGINE INNODB STATUS\G
- 发生死锁的事务 ID
- 执行的 SQL
- 加锁的情况
- 被回滚的事务
常见的死锁日志
------------------------
LATEST DETECTED DEADLOCK
------------------------
2025-09-08 21:05:45
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 25, OS thread handle 140146653595200, query id 1234 localhost root
UPDATE orders SET status='done' WHERE id=100;*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 123 page no 456 n bits 72 index `PRIMARY` of table `test`.`orders` trx id 12345 lock_mode X locks rec but not gap
Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 456 n bits 72 index `PRIMARY` of table `test`.`orders` trx id 12345 lock_mode X locks rec but not gap waiting*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 2 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 26, OS thread handle 140146653595600, query id 1235 localhost root
UPDATE orders SET status='done' WHERE id=200;*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 123 page no 456 n bits 72 index `PRIMARY` of table `test`.`orders` trx id 12346 lock_mode X locks rec but not gap
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 456 n bits 72 index `PRIMARY` of table `test`.`orders` trx id 12346 lock_mode X locks rec but not gap waiting*** WE ROLL BACK TRANSACTION (2)
3. 死锁的常见原因
-
事务访问顺序不一致
事务 A 先锁表 1 再锁表 2,事务 B 先锁表 2 再锁表 1。
-
长事务
占用大量行锁,导致其他事务长时间等待。
-
没有索引,导致锁范围扩大
扫描范围大 → 行锁变表锁或间隙锁,引发冲突。
- 外键约束或级联更新
父子表之间可能导致意料之外的加锁顺序。
4. 解决死锁的方法
(1)应用层面预防
- 统一访问顺序
保证不同事务访问同一组资源时,顺序一致。 - 尽量减少锁范围
通过索引精确过滤,避免无索引全表扫描加锁。 - 缩短事务时间
避免长事务,及时提交,减少锁占用。 - 分解大事务
把一个大事务拆成多个小事务提交。
(2)数据库配置层面
- 启用死锁检测(默认开启)
让 InnoDB 自动选择一个事务回滚。 - 合理设置超时时间
innodb_lock_wait_timeout
调小,可以更快释放死锁等待。
(3)发生死锁后的处理
- 应用重试机制
由于 MySQL 会回滚一个事务,应用需要捕获Deadlock found when trying to get lock
错误,然后 重试事务。