锁
大约 8 分钟
锁
- Mysql存储引擎
- MyISAM: 支持表级锁
- InnoDB: 不仅支持表级锁,还支持更细粒度的行级锁
锁分类
- 全局锁
- 加锁
flush tables with read lock
- 整个数据库就处于只读状态了: 数据和结构不允许更改
unlock tables
- 主要应用于做全库逻辑备份
- 避免全局锁影响业务,如何备份数据?
- 开启事务隔离级别 可重复读,操作前先开启事务
- 如 mysqldump 参数 –single-transaction 会在备份数据库之前先开启事务
- 备份期间备份的数据一直是在开启事务时的数据
- 加锁
- 表锁
- 行级锁
- 按隔离级别分
- 读已提交隔离级别:记录锁
- 可重复读隔离级别:记录锁、Gap Lock、Next-Key Lock
- Record Lock 记录锁
- 有 S 锁和 X 锁之分
- 一个事务 对一条记录 加 S锁,其他事务也 可以加 S锁,不可加 X 锁;
- S 型与 S 锁 兼容
- S 型与 X 锁 不兼容
- 一个事务 对一条记录 加 X锁,其他事务 不能加 X锁,也不可加 S锁;
- X 型与 X 锁 不兼容
- 当事务执行 commit 后,事务过程中生成的锁都会被释放。
- 一个事务 对一条记录 加 S锁,其他事务也 可以加 S锁,不可加 X 锁;
- 有 S 锁和 X 锁之分
- Gap Lock 间隙锁
- 只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象;
- 锁定一个范围,如锁定ID范围为(3,5),其他事务就无法插入ID 4
- 也有 有 S 锁和 X 锁之分,但是兼容的,无互斥关系;
- 因为间隙锁的目的是防止插入幻影记录而提出的。
- Next-Key Lock
- 临键锁
- Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。
- 既不能 插入ID 4,也不能修改 ID 5 记录
- 包含间隙锁+记录锁,
- 如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的。
- 包含间隙锁+记录锁,
- 插入意向锁
- 名字虽然有意向锁,但是它并不是意向锁,它是一种特殊的间隙锁。
- 该锁只用于并发插入操作
- 当事务B 提交插入新纪录,但被事务A加了 间隙锁
- 事物 B 会生成一个插入意向锁,然后将锁的状态设置为等待状态
- 如果说间隙锁锁住的是一个区间,那么「插入意向锁」锁住的就是一个点。
- 在间隙锁区间内与插入意向锁是不兼容的,反之可以
- 两个事务在同一时间内,一个拥有间隙锁,另一个无法拥有该间隙区间内的插入意向锁
- 按隔离级别分
加锁
- 普通的 select 语句是不会对记录加锁的(除了串行化隔离级别)
- 因为它属于快照读,是通过 MVCC(多版本并发控制)实现的
- 查询时加行级锁:称为锁定读
- 语句必须在一个事务中,因为当事务提交了,锁就会被释放
select ... lock in share mode;
对读取的记录加共享锁(S型锁)select ... for update;
对读取的记录加独占锁(X型锁)
update table .... where id = 1;
对操作的记录加独占锁(X型锁)delete from table where id = 1;
对操作的记录加独占锁(X型锁)
- 加锁的对象是索引,加锁的基本单位是 next-key lock
- next-key lock 是前开后闭区间
- 间隙锁 是 前开后开区间
- next-key lock 在一些场景下会退化成记录锁或间隙锁
- 在能使用记录锁或者间隙锁就能避免幻读现象的场景下, next-key lock 就会退化成记录锁或间隙锁。
- 假设 user 表,id主键索引,age 普通索引,name 无索引
- 唯一索引等值查询
- 当查询的记录是「存在」的,在索引树上定位到这一条记录后,将该记录的索引中的 next-key lock 会退化成「记录锁」
- 原因就是在唯一索引等值查询并且查询记录存在的场景下,仅靠记录锁也能避免幻读的问题。
- 当查询的记录是「不存在」的,在索引树找到第一条大于该查询记录的记录后,将该记录的索引中的 next-key lock 会退化成「间隙锁」
- 唯一索引等值查询并且查询记录不存在的场景下,仅靠间隙锁就能避免幻读的问题。
- 当查询的记录是「存在」的,在索引树上定位到这一条记录后,将该记录的索引中的 next-key lock 会退化成「记录锁」
- 唯一索引范围查询
- 大于等于:存在等值查询的条件,
- 如果等值查询的记录是存在于表中,那么该记录的索引中的 next-key 锁会退化成记录锁
- 小于或者小于等于
- 当条件值的记录不在表中:
- 不管是「小于」还是「小于等于」条件的范围查询,扫描到终止范围查询的记录时,该记录的索引的 next-key 锁会退化成间隙锁
- 其他扫描到的记录,都是在这些记录的索引上加 next-key 锁。
- 当条件值的记录在表中:
- 如果是「小于」条件的范围查询
- 扫描到终止范围查询的记录时,该记录的索引的 next-key 锁会退化成间隙锁
- 其他扫描到的记录,都是在这些记录的索引上加 next-key 锁;
- 如果「小于等于」条件的范围查询
- 扫描到终止范围查询的记录时,该记录的索引 next-key 锁不会退化成间隙锁。
- 其他扫描到的记录,都是在这些记录的索引上加 next-key 锁。
- 如果是「小于」条件的范围查询
- 当条件值的记录不在表中:
- 大于等于:存在等值查询的条件,
- 非唯一索引等值查询 ,存在两个索引,一个是主键索引,一个是非唯一索引
- 查询的记录「存在」时
- 对扫描到的二级索引记录加的是 next-key 锁
- 对于第一个不符合条件的二级索引记录,next-key 锁会退化成间隙锁
- 在符合查询条件的记录的主键索引上加记录锁
- 当查询的记录「不存在」时
- 第一条不符合条件的二级索引记录,next-key 锁会退化成间隙锁
- 因为不存在满足查询条件的记录,所以不会对主键索引加锁。
- 查询的记录「存在」时
- 非唯一索引范围查询
- 非唯一索引范围查询,索引的 next-key lock 不会有退化为间隙锁和记录锁的情况
- 非唯一索引进行范围查询时,对二级索引记录加锁都是加 next-key 锁。
- 没有加索引的查询
- 每一条记录的索引上都会加 next-key 锁
- 相当于锁住的全表,这时如果其他事务对该表进行增、删、改操作的时候,都会被阻塞。
- update 和 delete 语句如果查询条件不加索引
- 那么由于扫描的方式是全表扫描,于是就会对每一条记录的索引上都会加 next-key 锁,这样就相当于锁住的全表。
- 唯一索引等值查询
锁兼容关系
加锁分析
- select ... for update 独占锁(X型锁)
- 首先:加锁的基本单位是 next-key lock
- 通过数据库查询锁参考
- 查询实际加锁情况,是否是next-key lock,还是退化成其他锁;
- 不同索引,不同查询条件锁情况分析
锁查询
查询事务的锁:
select * from performance_schema.data_locks
LOCK_TYPE:锁类型
- RECORD 表示行级锁,而不是记录锁的意思
- TABLE 表锁
- LOCK_MODE:
- X:X 型的 next-key 锁
- X, REC_NOT_GAP: X 型的记录锁
- X, GAP: X 型的间隙锁
- IX: X 型的意向锁
- X, GAP, INSERT_INTENTION: 插入意向锁
- LOCK_DATA: 锁的范围最右值,如间隙锁,例如 LOCK_DATA:30,
- 最左值为:表中已有记录中30之前的记录值
LOCK_DATA: 30 范围为 [20, 30]
死锁
- 场景前提:InnonDB存储引擎,可重复读隔离级别,关闭死锁检测
- 举例订单业务
- 新增订单,增加幂等性校验,防止订单重复
- 同时有两个事务,先查询,再新增
select ... for update
- select .. update 防止事务执行的过程中,有其他事务插入了记录,而出现幻读的问题
- 如果使用普通的select会出现幻读问题:不会对记录加锁
- select ... update 加了什么锁:
- 表锁:X 类型的意向锁
- 行锁:X 类型的间隙锁
- 间隙锁与间隙锁之间是兼容
- 所以所以两个事务中 select ... for update 语句并不会相互影响。
- 同时有两个事务进行新增订单
- 陷入了等待状态,即死锁
- 都无法进行 insert
- 为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,导致死锁。
- 行锁的释放
- 在事务提交(commit)后,锁就会被释放,并不是一条语句执行完就释放行锁。
避免死锁
- 数据库方面
- 设置事务等待锁的超时时间,InnoDB,innodb_lock_wait_timeout ,默认值时 50 秒,超时后会回滚该事务从而释放锁;
- 开启主动死锁检测,主动回滚死锁链条中的某一个事务,innodb_deadlock_detect为on;
- 业务层面
- 如新增订单,先查询select .. update,后新增,造成死锁
- 可以进行订单号加唯一索引,直接update,重复抛出异常,完成订单的幂等性校验逻辑;
- 如新增订单,先查询select .. update,后新增,造成死锁