1. 两段锁
在一段代码开始执行之前,如果我们可以明确地知道哪些数据将会被使用到,我们就可以在执行前一次性地将所有可能会用到地资源进行锁定,在全部执行完毕之后再一次性全部解锁。这种方式可以有效避免循环死锁,但是,在数据库中,我们无法在事务开始阶段知道哪些数据会被使用到,因此,数据库遵循两段锁协议。
根据百度百科,两段锁协议是指每个事务地执行可以分为生长阶段(加锁阶段)和衰退阶段(解锁阶段)。
在加锁阶段,可以进行加锁操作,在对任何数据进行读操作之前需要申请S锁(共享锁,其他事务可以继续加共享锁,但不再允许加排他锁),在进行写操作之前需要申请并获取X锁(排他锁,不再允许其他事务申请任何锁)。
在解锁阶段,当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
两段锁地实现:事务开始后就处于加锁阶段,一直到执行ROLLBACK和COMMIT之前都是加锁阶段。ROLLBACK和COMMIT使事务进入解锁阶段,即在ROLLBACK和COMMIT模块中DBMS释放所有封锁。
与一段锁不同的是,两段锁不要求事务一次性将所有可能用到的数据全部加锁,而是在对其进行操作时才申请,所以两段锁有可能会引发死锁。但是两段锁协议可以保证事务的并发调度是串行化的。
2. MySQL中锁的类型
1. 表级锁和行级锁(锁的粒度)
表级锁和行级锁代表着不同的锁的粒度,表级锁粒度大,实现逻辑简单,获取和释放时开销小,可以避免死锁,但是会降低并发性能;行级锁粒度小,但是实现逻辑复杂,获取和释放时开销大,同时容易发生死锁,但是并发性能更好。
此外,还有其他引擎如BDB实现了页级锁,这种锁速度快,但是冲突多。
关于表级锁和行级锁,在MySQL存储引擎中,MyISAM引擎只支持表级锁,而InnoDB引擎同时支持表级锁和行级锁,默认粒度是行级锁。
2. 共享锁和排他锁(是否可写)
关于共享锁和排他锁相当于读锁和写锁,他是InnoDB引擎实现的一种行级锁
- S锁/共享锁,其他事务可以继续加共享锁,但不再允许加排他锁
-
3. 意向锁
意向锁包括意向共享锁和意向排他锁,是一种表级锁。
意向锁提供了一种表级锁和行级锁共存的方案。
《MySQL锁:灵魂七拷问》中提到,当我们需要添加一个表级锁时,需要判断这个表中是否有行已经被所锁住,效率最低的办法是遍历所有记录判断是否有行级锁,但是更加高效的方法是:在申请小粒度的行级锁之前,需要先获取一个假的表锁,表示希望锁住这张表中的某些记录,之后MySQL在判断表中是否有记录被锁定时,只需要判断是否有事务申请了这个假的表锁,也就是意向锁。
也就是说,意向锁用来指示事务稍后会对表中的行加哪种锁(共享锁还是排他锁)。4. 锁之间的兼容性
| | X | IX | S | IS | | :—-: | :—-: | :—-: | :—-: | :—-: | | X | × | × | × | × | | IX | × | √ | × | √ | | S | × | × | √ | √ | | IS | × | √ | √ | √ |
×代表两种锁之间存在冲突,√代表可以兼容
- 可以看出X排他锁不与任何一种锁兼容,意向锁之间都是兼容的
如果锁与现有锁兼容,则将其授予请求的事务,但如果与现有锁冲突,则不授予锁。事务等待直到冲突的现有锁被释放。如果锁定请求与现有锁定发生冲突,并且由于可能导致死锁而无法被授予,则会发生错误
3. InnoDB中的锁算法
1. InnoDB中行级锁的实现方式
InnoDB中行级锁是通过给索引上的索引项加锁来实现的,也就是说,只有通过索引条件来检索数据,InnoDB才会使用行级锁,否则InnoDB将使用表锁(另有说法是对所有记录行加行级锁)。
2. InnoDB中行级锁的分类
- Record Lock:单个记录上的锁,对索引项加锁,锁定符合条件的行,其他事务不能修改和删除加锁项
- Gap Lock:间隙锁,对索引项之间的“间隙”进行加锁,锁定记录的范围(对第一条记录前的间隙或者是最后一条记录后的间隙加锁),不包括记录本身。不允许其他的事务在锁范围内插入数据,防止出现幻影行。
- Next Key Lock:是上述两种锁的结合,锁定范围包括索引项本身和索引项之间的空隙,可以解决幻读。
4. MySQL中的隔离级别与锁
1. Read Committed读已提交
在Read Committed中,数据的读取是不加锁的,但是数据的写入、删除、修改是需要加锁的。
看一下RC是怎样解决脏读问题的:
| 事务A | 事务B |
|---|---|
begin; |
begin; |
update user set age = 18 where id = 1; |
update user set age = 99 where id = 1; |
commit; |
事务A开启时对id=1的数据行加排他锁,并在提交之前这个锁并不会释放,事务B无法获取写锁,也就无法对其进行操作,直到事务B超时,因此避免的脏读的问题。
2. Repeatable Read可重复读
读
在RR中,使用多版本并发控制机制(MVCC)来使得数据变得可重复读(见MySQL事务部分)。但是,这种方式读取到的数据实时性差,在对数据时效敏感的场景下容易出现问题。
对于读取历史数据的方式,我们叫他快照读(snapshot read),而读取数据库当前版本数据的方式,叫做当前读(current read),显然在MVCC中:
- 快照读:
select * from table...; - 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁
参考文章:
[1] https://blog.csdn.net/qq_34337272/article/details/80611486 MySQL锁机制简单了解
[2] https://tech.youzan.com/seven-questions-about-the-lock-of-mysql/ MySQL锁:灵魂七拷问
[3] https://dev.mysql.com/doc/ MySQL参考手册
[4] https://tech.meituan.com/2014/08/20/innodb-lock.html Innodb中的事务隔离级别和锁的关系
