[MySQL] InnoDB 锁

274 阅读8分钟

[MySQL] InnoDB 锁

一、锁的类型

1. 两种标准的行级锁:

  • 共享锁(S Lock) 允许事务读取一行记录
  • 排他锁(X Lock) 允许事务删除或更新一行记录

2. 两种标准的表级锁:

  • 意向共享锁(IS Lock) 事务想要获得表中某几行记录的意向锁
  • 意向排他锁(IX Lock) 事务想要获得表中某几行记录的意向锁

3.锁的兼容性:

SXISIX
S
X
IS
IX

简单规则描述:IS/IX互相兼容,只要有X锁必然不兼容,IS锁兼容性最强。

一致性非锁定读:

 一致性非锁定度是指,即使有事务正在对数据进行update或者delete操作,仍然能够读取数据,读取的是记录的快照,不必等待锁释放,是一种乐观的锁控制技术。

 与锁定读不同的,锁定读是指有事务正在对数据进行update或者delete操作的时候加上X锁,那么别的事务就无法获得锁,进而无法读取产生阻塞,这是一种悲观的锁控制技术,性能上较差,但是一致性和完整性上较强。

 快照数据(Snapshot)是指该记录之前的数据版本,通过undo段(回滚段)来实现,读取undo段中的数据是不需要加锁的,因为仅是读取操作而已。

 快照数据可能会有多个版本,因此这种版本控制技术被称为多版本控制技术(MVCC, Multi Version Concurrency Control)。

InnoDB默认的读取方式就是一致性非锁定读,在不同的隔离级别下,采用的快照定义不一样。

  • READ COMMITED(已提交读)

 已提交读读取的快照是指锁定操作事务的最新版本快照。

  • REPEATABLE READ(可重复读)

 可重复读读取的快照是指锁定操作事务开始的最初版本快照。

一致性锁定读:

 一致性锁定读就是每次对数据操作的时候都加锁,能够保证数据的一致性。

  • 读取操作

SELECT …… LOCK IN SHARE MODE 对记录加S锁

  • 写入操作

SELECT …… FOR UPDATE 对记录加X锁


二、锁的算法

 三种行锁的算法:

  • Record Lock: 单个行记录上的锁

 记录锁总是会锁定索引记录,如果没有创建索引,会锁定隐式索引(即_rowid索引)。

  • Gap Lock: 间隙锁,锁定一个范围,但不包含记录本身
  • Next-key Lock: Gap Lock + Record Lock 锁定范围+记录本身

  解决Phantom Problem问题:

 幻读是指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行

 InnoDB采用Next-key Lock的方式来解决了幻读的问题,前提是隔离级别为REAPTABLE READ。

三、锁的问题

 1.脏读

 脏读是指事务读到了别的事务没有提交的数据。在

 在读已提交的隔离级别下,InnoDB引擎采用的非一致性读,使用读取最新快照的方式是产生脏读的根本原因。

 2.不可重复读:

 不可重复读是一个事务内多次读取同一数据集合。在这个事务还没结束时,另一事务也访问该数据集,并做了一些DML操作。第一次事务两次数据读取之间,由于第二个事务的修改,导致两次读取到的数据集不一致。

 不可重复读和脏读的区别在于:脏读读取到的是未提交的数据,不可重复读读取到的是已提交的数据。

 在InnoDB中,通过Next-key Lock方式解决了不可重复读的问题,在MySQL官方文档中将不可重复读定义为幻读。在Next-key Lock算法下,针对索引的扫描不仅锁住索引,而且锁住索引覆盖的范围(gap),因此在范围内的插入都是不允许的,这样就避免了数据插入造成的不可重复读问题。

 3.丢失更新

 丢失更新是指一个事务的更新被另外一个事务的更新操作覆盖,导致数据不一致。

注意:这里的丢失更新并非在数据库层面造成的,而是在应用层面造成的。

 在应用层面针对数据的操作如果采用如下流程:从数据库读取到内存->更新内存数据->写入数据库,两个同时进行的操作就可能会导致丢失更新。

 这种情况的避免就需要在读取的时候增加X锁。

四、死锁

 概念

 所谓死锁就是两个事务互相持有对方请求的锁资源,造成无法获取到自己所需要的锁资源进而一直阻塞等待的情况。

 死锁处理

 针对死锁的处理有两种方式:

  • 被动方式->等待锁超时回滚

 这种方式之所以被称为被动处理方式是因为并没有针对死锁进行任何操作,而是简单的等待超时,超时后针对具体情况对造成死锁的事务进行回滚。

 因此这里产生的另外一个问题就是到底回滚哪个事务?回滚处理方式比较复杂。  常见的有FIFO谁先来的先回滚谁,但是问题是可能先来的事务undo段比较大,那么回滚起来比较耗时,这样就不能够尽快解决死锁的问题,所以一般采用的是回滚undo段比较小的事务。

  • 主动方式->死锁检测

 即wait-for graph等待图模式,等待图中包含两种信息:

  • 等待事务链表

  • 锁的信息链表

 等待图就是将事务作为图中的一个个节点,A等待B持有的锁资源则用一个从A出发指向B的有向边来表示,这样针对死锁的检测就转换成为了一个在一个有向图中找回路的问题。

 等待图是一种主动的检测方式,如果检测到有死锁发生,那么直接回滚undo段较小的事务即可,这样就可最快的解决死锁问题。

 另外一个问题是在有向图中寻找回路的问题,死锁的检测是采用深度优先的算法来实现的,在InnoDB1.2版本之前采用的是递归的方式实现的,1.2版本之后进行了优化将递归方式改为了非递归方式来实现。

五、不同隔离级别对应的锁使用方式

 这里可以直接参考官方文档就可以了,我担心自己翻译的不准确,所以把英文原文附上了。

  1. 读未提交(READ UNCOMMITTED)

 select语句读取方式采用非锁定方式来读取,可能会读取到一个比较早的行记录版本,因此使用这种隔离级别读取到的数据可能是不一致的,也就是脏读。

SELECT statements are performed in a non-locking fashion, but a possible earlier version of a record might be used. Thus, using this isolation level, such reads are not consistent. This is also called a “dirty read.” Otherwise, this isolation level works like READ COMMITTED.

  1. 读已提交(READ COMMITTED)

 读仅锁定行,不锁定行之间的间隙(gap),范围类型的UPDATE和DELETE,设置下一个键或间隙锁,并阻止其他用户插入该范围所覆盖的间隙。因为必须阻止“幻像行”才能使MySQL复制和恢复正常工作。

 读操作读取的是最新的快照。

A somewhat Oracle-like isolation level. All SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE statements lock only the index records, not the gaps before them, and thus allow the free insertion of new records next to locked records. UPDATE and DELETE statements using a unique index with a unique search condition lock only the index record found, not the gap before it. In range-type UPDATE and DELETE statements, InnoDB must set next-key or gap locks and block insertions by other users to the gaps covered by the range. This is necessary because “phantom rows” must be blocked for MySQL replication and recovery to work. Consistent reads behave as in Oracle: Each consistent read, even within the same transaction, sets and reads its own fresh snapshot. See Section 14.2.10.4, “Consistent Non-Locking Read”.

  1. 可重复读(REPEATABLE READ)

 InnoDB的默认隔离级别,唯一查询下读加S锁,更新(UPDATE和DELETE)锁行记录,范围查询使用Next-key Lock,锁定扫描到的范围和记录。

 快照的定义上这里是指其他事务开始时读取到的行记录。

This is the default isolation level of InnoDB. SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE, and DELETE statements that use a unique index with a unique search condition lock only the index record found, not the gap before it. With other search conditions, these operations employ next-key locking, locking the index range scanned with next-key or gap locks, and block new insertions by other users. In consistent reads, there is an important difference from the READ COMMITTED isolation level: All consistent reads within the same transaction read the same snapshot established by the first read. This convention means that if you issue several plain SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other. See Section 14.2.10.4, “Consistent Non-Locking Read”.

  1. 串行化(SERIALIZABLE)

 即所有读操作都加S锁。

This level is like REPEATABLE READ, but InnoDB implicitly commits all plain SELECT statements to SELECT ... LOCK IN SHARE MODE.

六、参考文档

  1. MySQL技术内幕InnoDB存储引擎第2版
  2. web.archive.org/web/2006061…