1. 两段锁

在一段代码开始执行之前,如果我们可以明确地知道哪些数据将会被使用到,我们就可以在执行前一次性地将所有可能会用到地资源进行锁定,在全部执行完毕之后再一次性全部解锁。这种方式可以有效避免循环死锁,但是,在数据库中,我们无法在事务开始阶段知道哪些数据会被使用到,因此,数据库遵循两段锁协议
根据百度百科,两段锁协议是指每个事务地执行可以分为生长阶段(加锁阶段)和衰退阶段(解锁阶段)
在加锁阶段,可以进行加锁操作,在对任何数据进行读操作之前需要申请S锁(共享锁,其他事务可以继续加共享锁,但不再允许加排他锁),在进行写操作之前需要申请并获取X锁(排他锁,不再允许其他事务申请任何锁)。
在解锁阶段,当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
两段锁地实现:事务开始后就处于加锁阶段,一直到执行ROLLBACK和COMMIT之前都是加锁阶段。ROLLBACK和COMMIT使事务进入解锁阶段,即在ROLLBACK和COMMIT模块中DBMS释放所有封锁。
与一段锁不同的是,两段锁不要求事务一次性将所有可能用到的数据全部加锁,而是在对其进行操作时才申请,所以两段锁有可能会引发死锁。但是两段锁协议可以保证事务的并发调度是串行化的。

2. MySQL中锁的类型

1. 表级锁和行级锁(锁的粒度)

表级锁和行级锁代表着不同的锁的粒度,表级锁粒度大,实现逻辑简单,获取和释放时开销小,可以避免死锁,但是会降低并发性能;行级锁粒度小,但是实现逻辑复杂,获取和释放时开销大,同时容易发生死锁,但是并发性能更好
此外,还有其他引擎如BDB实现了页级锁,这种锁速度快,但是冲突多。
关于表级锁和行级锁,在MySQL存储引擎中,MyISAM引擎只支持表级锁,而InnoDB引擎同时支持表级锁和行级锁,默认粒度是行级锁。

2. 共享锁和排他锁(是否可写)

关于共享锁和排他锁相当于读锁和写锁,他是InnoDB引擎实现的一种行级锁

  • S锁/共享锁,其他事务可以继续加共享锁,但不再允许加排他锁
  • X锁/排他锁,不再允许其他事务申请任何锁

    3. 意向锁

    意向锁包括意向共享锁和意向排他锁,是一种表级锁
    意向锁提供了一种表级锁和行级锁共存的方案。
    《MySQL锁:灵魂七拷问》中提到,当我们需要添加一个表级锁时,需要判断这个表中是否有行已经被所锁住,效率最低的办法是遍历所有记录判断是否有行级锁,但是更加高效的方法是:在申请小粒度的行级锁之前,需要先获取一个假的表锁,表示希望锁住这张表中的某些记录,之后MySQL在判断表中是否有记录被锁定时,只需要判断是否有事务申请了这个假的表锁,也就是意向锁
    也就是说,意向锁用来指示事务稍后会对表中的行加哪种锁(共享锁还是排他锁)

    4. 锁之间的兼容性

    | | X | IX | S | IS | | :—-: | :—-: | :—-: | :—-: | :—-: | | X | × | × | × | × | | IX | × | √ | × | √ | | S | × | × | √ | √ | | IS | × | √ | √ | √ |

  • ×代表两种锁之间存在冲突,√代表可以兼容

  • 可以看出X排他锁不与任何一种锁兼容,意向锁之间都是兼容的
  • 如果锁与现有锁兼容,则将其授予请求的事务,但如果与现有锁冲突,则不授予锁。事务等待直到冲突的现有锁被释放。如果锁定请求与现有锁定发生冲突,并且由于可能导致死锁而无法被授予,则会发生错误

    3. InnoDB中的锁算法

    1. InnoDB中行级锁的实现方式

  • InnoDB中行级锁是通过给索引上的索引项加锁来实现的,也就是说,只有通过索引条件来检索数据,InnoDB才会使用行级锁,否则InnoDB将使用表锁(另有说法是对所有记录行加行级锁)

    2. InnoDB中行级锁的分类

  1. Record Lock:单个记录上的锁,对索引项加锁,锁定符合条件的行,其他事务不能修改和删除加锁项
  2. Gap Lock:间隙锁,对索引项之间的“间隙”进行加锁,锁定记录的范围(对第一条记录前的间隙或者是最后一条记录后的间隙加锁),不包括记录本身。不允许其他的事务在锁范围内插入数据,防止出现幻影行。
  3. 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...;
  • 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁
    • select * from table where ? lock in share mode;
    • select * from table where ? for update
    • insert
    • update
    • delete
      写(“当前读”)
      为了解决当前读中出现的幻读问题,MySQL使用了NextKey锁,关于这部分内容,可以看[4]

      3. Serializable

      在可序列化隔离级别下,所有事务被强制串行执行,读加共享锁,写加排他锁,使用的全部为悲观锁,数据安全,实现简单,但是并发能力差。

参考文章:
[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中的事务隔离级别和锁的关系