MySQL之InnoDB锁机制

InnoDB存储引擎中的锁

锁的类型

InnoDB存储引擎实现了如下两种标准的行级锁

  1. 共享锁(S Lock):允许事务读一行数据
  2. 排他锁(X Lock):允许事务删除或更新一行数据

如果一个事务 T1 已经获得了行 r 的共享锁,那么另外的事务 T2 可以立即获得行 r 的共享锁,因为读取并没有改变行 r 的数据,称这种情况为锁兼容。但若有其他的事务 T3 想获得行 r 的排他锁,则其必须等待事务T1、T2释放行 r 上的共享锁这种情况称为锁不兼容。即共享锁不互斥获取,排他锁互斥获取。

共享锁与排他锁的共存问题

接下来我们考虑这样一个问题:
事务A锁住了表中的一行,让这一行只能读,不能写。之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
为了解决这个冲突,数据库需要做两个判断:

  1. 判断表是否已被其他事务用表锁锁表;
  2. 判断表中的每一行是否已被行锁锁住。

由于第二个判断条件效率低下,因此引入了意向锁的机制。
意向锁是为了解决程序执行高效性问题,并不是为了多并发下数据的一致性问题。

意向锁

InnoDB 存储引擎支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB 存储引擎支持一种额外的锁方式,称之为意向锁。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁
意向锁分为两种:

  1. 意向共享锁(IS Lock):事务想要获得一张表中某几行的共享锁;
  2. 意向排他锁(IX Lock):事务想要获得- -张表中某几行的排他锁。

首先让我们看看数据库数据存储结构的层次图:
MySQL之InnoDB锁机制_第1张图片
有了意向锁后,考虑1.2中我们所说的情况,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。此时,如果事务B要申请整个表的写锁,判断条件就变成如下两个条件:

  1. 判断表是否已被其他事务用表锁锁表(不变);
  2. 发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。

注意:意向锁(IX,IS)是表级锁,不会和行级的排他锁(X)或 共享锁(S)发生冲突。只会和表级的锁(X,S)发生冲突; 行级别的X和S按照普通的共享、排他规则即可。只要写操作不是同一行,就不会发生冲突。

多版本并发控制(MVCC)

数据库在针对多并发问题,提出了多版本控制的概念;核心原理是:

  1. 写任务发生时,将数据克隆一份,以版本号区分;
  2. 写任务操作新克隆的数据,直至提交;
  3. 并发读任务可以继续读取旧版本的数据,不至于阻塞;

MySQL之InnoDB锁机制_第2张图片
如上图所示:

  1. 最开始数据的版本是V0;
  2. T1时刻发起了一个写任务,这是把数据clone了一份,进行修改,版本变为V1,但任务还未完成;
  3. T2时刻并发了一个读任务,依然可以读V0版本的数据;
  4. T3时刻又并发了一个读任务,依然不会阻塞;

可以看到,数据多版本,通过“读取旧版本数据”能够极大提高任务的并发度

一致性锁定读

此外,某些情况下,我们需要显式地对数据库读取操作,进行加锁以保证数据逻辑的一致性,这要求数据库支持加锁语句,
持加锁语句,即使是对于SELECT的只读操作。InnoDB存储引擎对于SELECT语句支
持两种一致性的锁定读 (locking read)操作:
1. SELECT … FOR UPDATE
2. SELECT … LOCK IN SHARE MODE

SELECT…FOR UPDATE对读取的行记录加一个X锁,其他事务不能对已锁定的行加上任何锁。SELECT…LOCK IN SHARE MODE对读取的行记录加一个S锁,其他事务可以向被锁定的行加S锁,但是如果加X锁,则会被阻塞。
对于一-致性非锁定读,即使读取的行已被执行了SELECT…FOR UPDATE,也是可以进行读取的,这和之前讨论的情况一样。此外,SELECT…FOR UPDATE, SELECT…LOCK IN SHARE MODE必须在一个事务中,当事务提交了,锁也就释放了。因此在使用.上述两句SELECT锁定语句时,务必加上BEGIN, START TRANSACTION或者SET AUTOCOMMIT=0。

你可能感兴趣的:(mysql,mysql,数据库)