概述#
并发事务访问#
并发事务访问的情况#
读 - 读#
并发读,不需要锁
写 - 写#
并发写,会出现脏写问题,而脏写问题不能容忍
解决:先到的事务需加锁,后到的事务需等待,直到锁释放再去加锁执行
加锁时,创建锁结构,会关联事务 id 等
读 - 写 或 写 - 读#
一个读,一个写,可能发生脏读、不可重复度、幻读问题
并发问题的解决方案#
方案一:读操作利用多版本并发控制(MVCC),写操作加锁#

方案二:读写操作都采用加锁的方式#

方案一、二简单对比#

锁不同角度分类#
从数据操作的类型划分#
共享锁和排他锁

获取了 S 锁后,其他事务可以继续获得 S 锁,但不能获得 X 锁除非 S 锁已释放
获取了 X 锁后,其他事务不能继续获得 S/X 锁
读操作#
要么获取 S 锁,要么获取 X 锁(有些场景的读操作也需要排他性)
获取共享锁的方式读
Select * from tb_user lock in share mode;
Select * from tb_user for share; (8.0 版本)
获取排他锁的方式读
Select * from tb_user for update;

写操作#
包含 insert、delete、update 操作
Delete 实际上是加 X 锁修改标记
Update 有 3 种情况:
- 修改主键字段,delete + insert,加 X 锁和隐式锁;
- 修改非主键字段,且字段数据占用存储不变,加 X 锁;
- 修改非主键字段,且字段数据占用存储有变,delete + insert,加 X 锁和隐式锁;
Insert 实际上是加隐式锁

从数据操作的粒度划分#

表锁#

表级别的 S 锁和 X 锁#

查看表加的锁
Show open tables
手动加锁
Lock tables tb_user [read | write]
Myisam 下的表级 S 锁和 X 锁#

意向锁 Intention Lock#


意向锁要解决的问题#
考虑一个场景:事物 A 对某一行记录添加了排他锁,此时事务 B 需要对该行所在的表添加锁,那么事务 B 如何判断 该表已经被加过锁? 难道事务 B 需要对该表一行一行判断是否有行级锁? 显然不是
实际上是事物 A 对某一行添加排他锁的同时,在行级锁的上层如页锁、表锁均添加了意向锁,事务 B 只需要判断该表有没有意向锁即可

特点#

自增锁 AUTO-INC Lock#

3 种锁定模式#



元数据锁 MDL Lock#
其实就是针对于表结构/表元数据的锁,分为 MDL 读锁和 MDL 写锁
MDL 读锁就是不影响表结构时添加的,对表数据的增删改查不涉及表结构的修改
MDL 写锁就是需要修改表结构时需要添加的

行锁#

记录锁 Record Lock#

间隙锁 Gap Lock#


间隙锁随着对行记录加 S 锁时自动加上
select * from user where id = 1 for share ;
查看锁(显式锁)
Select * from performance_schema.data_locks\G
查看显示锁,insert 的隐式锁看不到的
间隙锁会出现死锁问题,当两个事务均拥有某个范围的间隙锁、且在进行插入操作时,就会出现死锁
解决死锁策略(下面死锁部分详述)#
- 各自等待直至某一方超时,超时方自我回滚且释放锁
- 检测机制检测到死锁后,回滚某个事务,释放其持有的锁,即手动破坏死锁

临键锁 Next-Key Lock#
其实就是 行记录锁 + 间隙锁
当给一个范围时,这个范围内匹配的数据行会加记录锁,间隙部分会加间隙锁
插入意向锁 Insert Intention Lock#
是在 insert 操作时产生的。是一种特殊的间隙锁


页锁#

从对待锁的态度划分#

秒杀案例 - 不使用锁

悲观锁 Pessimistic Lock#
适合写操作多的场景,加锁具有排他性

秒杀案例 - 使用悲观锁


使用悲观锁,要确定使用索引,否则会导致因顺序扫描使当前事务不需要数据行也上锁,开销大也影响并发性能
乐观锁 Optimistic Lock#
适合读操作多的场景,无死锁

乐观锁的版本号机制#

乐观锁的时间戳机制#

秒杀案例 - 使用乐观锁


两种锁的对比、适用场景#

按加锁的方式划分#
隐式锁#



对隐式锁的理解:#
insert 时不加锁(含隐式锁,无锁),可以避免加锁的开销,当其他事务来读写时给当前事务的 insert 加锁(转化成显式锁),是乐观态度的体现,当出现情况时再悲观处理。而在 insert 过程中,其他事务来读写是可能发生的,若不发生就减少了加锁的开销,若发生则加锁,这就是延迟加锁!
对 insert 的情况,理解 隐式锁 与 插入意向锁#
插入意向锁,是当前事务想 insert 却发现存在临键锁时,会加的一个锁
而隐式锁,是当前事务 A 进行 insert (进来时未被干扰即没有临键锁,产生了隐藏列)且还未提交时,含有隐式锁/无锁(查锁查不出来的,内存中不存在的),另一个事务 B 进来了发现该隐藏列中的 trx_id(事务 A)依然活跃,为事务 A 创建了锁。同时事务 B 也为自己创建一个锁进行等待
显式锁#
通过 Select * from performance_schema.data_locks 能查看得到的
通过特定语句加的锁
Select .. For share;
Select … For update;
全局锁#

死锁#
双方都持有对方的资源、双方都在等待对方释放,双方都不主动释放对方的资源
产生死锁的必要条件#

如何处理死锁#

超时等待#

死锁检测#




如何避免死锁#

锁的内存结构#
符合以下条件的记录会放在一个锁结构中:

锁结构#







锁监控#

其他监控方法#
三张表#
5.7 之前,
使用 information_schema 库的表 innodb_trx、innodb_locks、innodb_lock_waits 表
其中,innodb_locks 表中只能看到阻塞事务的锁,不能看到未阻塞事务的锁
8.0 后,
沿用 information_schema.innodb_trx,
并使用 performance_schema.data_locks(所有的锁) 代替了原先的 information_schema.innodb_locks,除了能看到阻塞事务的锁,未阻塞事务的锁也能看到了;
使用 performance_schema.innodb_lock_waits(等待的锁) 代替了原先的 information_schema.innodb_lock_waits