《MySql技术内幕 InnoDb存储引擎》学习笔记【七 锁】

版权声明:本文为博主原创,转载请注明出处! https://blog.csdn.net/greedystar/article/details/87864950

目录

七 锁

(一)Lock与Latch

(二)InnoDB中的锁

1 锁的类型

2 一致性非锁定读

3 一致性锁定读

4 自增长和锁

5 外键和锁

(三)锁的算法

(四)锁问题

1 脏读

2 不可重复读

3 幻读

4 丢失更新

(五)阻塞

(六)死锁


七 锁

锁是数据库系统区别于文件系统的一个关键特性,用于管理对共享资源(不仅包含行记录,还包含缓冲池中的LRU列表等)的并发访问。

InnoDB锁的实现和Oracle类似,提供一致性的非锁定读、行级锁。

(一)Lock与Latch

在InnoDB中,Lock的对象是事务,用来锁定数据库中的对象,如表、页、行等,一般Lock在事务commit或rollback后才释放(与具体的事务隔离级别有关),且是有死锁机制的。

Latch一般称为轻量级锁,在InnoDB中可以分为mutex和rwlock,目的是用来保证并发线程操作临界资源的正确性,通常没有死锁检测机制,如JVM的内部锁。

(二)InnoDB中的锁

1 锁的类型

InnoDB实现了两种标准的行级锁:

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

共享锁和排它锁的兼容性:

InnoDB还支持多粒度的锁,允许事务在行级和表级上的锁同时存在,于是提供了意向锁,意向锁是将锁定的对象分为多个层次,按层次由粗到细进行锁定。InnoDB中意向锁的实现比较简单,是表级别的锁,目的是为了在一个事务中揭示下一行将被请求的锁类型。

InnoDB实现了两种表级意向锁:

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

例如,我们想对记录r加X锁,那么首先需要对表、段、区、页等加IX锁,最后才对记录r上X锁。

兼容性:

2 一致性非锁定读

一致性非锁定读是指存储引擎通过行多版本控制的方式来读取当前数据库中的数据,如果当前正在DELETE或UPDATE操作,读操作也不会阻塞,而是会读行的一个快照数据。

如上图所示,读取请求需要读取的数据被X锁锁定,该读取请求不会被阻塞,而是读取了数据的一个快照,由于快照是数据的历史版本,没有操作会对历史版本进行修改,所以读取快照的操作是不需要上锁的。

在上图中,我们看到快照数据是可能存在多个版本的,这种机制通常称为多版本技术,由此带来的并发控制通常称为多版本并发控制(Multi Version Concurrency Control,MVCC)。

虽然一致性非锁定读是读取快照数据,但在不同的事务隔离级别下,对于快照的定义是不同的,比如REPEATABLE READ(InnoDB默认的事务隔离级别),读取的快照数据总是事务开始时的版本,而READ COMMITTED每次读取都是读取最新版本的快照数据。

3 一致性锁定读

在一些情况下,用户需要显式的对数据库读取操作进行加锁,以保证的一致性。InnoDB提供了两种SELECT语句的一致性锁定读:

  1. SELECT * FOR UPDATE:对读取的行记录加一个X锁
  2. SELECT * LOCK IN SHARE MODE:对读取的行记录加一个S锁

这两种查询必须在一个事务中,当事务提交了,锁才释放。

4 自增长和锁

InnoDB中,对每个含有自增长值的表都有一个自增长计数器,插入操作会根据这个自增长计数器的值加1赋予新记录的自增长列,这种实现方式称作AUTO-INC Locking,这种锁是一种特殊的表锁机制,锁定在执行完自增长值的Insert SQL语句后立即释放,而不是在事务提交后释放。

虽然自增长值的插入锁不需要等待事务提交,但在并发插入时必须等待插入SQL执行完成,在插入大量数据时会导致其他插入操作被阻塞,为此,MySql5.1.22版本开始,InnoDB提供了一种轻量级互斥量的自增长机制。

5 外键和锁

在对外键列的插入或更新操作时,首先需要查询父表中的记录,对父表的查询不是使用一致性非锁定读的方式,而是使用SELECT * LOCK IN SHARE MODE,即主动对父表加S锁。

(三)锁的算法

在InnoDB中有3种行锁的算法,分别是:

  1. Record Lock:单个行记录上的锁
  2. Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
  3. Next-Key Lock:Gap + Record Lock,锁定一个范围,并且锁定记录本身

(四)锁问题

1 脏读

脏读:一个事务可以读到另一个事务中未提交的数据,这显然违反了ACID中的隔离性。

脏读的现在通常不会出现,只有当事务的隔离级别为READ UNCOMMITTED时才会发生,目前主流数据库的事务隔离级别都不设置为READ UNCOMMITTED,InnoDB的默认隔离级别为READ REPEATABLE。

2 不可重复读

不可重复读:在一个事务中多次读取同一数据记录,由于其他事务的修改,导致多次读取的结果不一致,主要针对delete和update操作。

3 幻读

幻读:在一个事务中多次读取同一数据集合,由于其他事务的新增,导致多次读取的结果可能会出现一些新的数据,主要针对insert操作。

InnoDB中通过Next-Key Lock算法避免幻读,MySql官方定义为Phantom Problem,即幻像问题。在Next-Key Lock算法下,对于索引的扫描,不仅是锁住扫描到的索引,还锁住这些索引覆盖的范围,避免了不可重复读,但需要注意,普通的SELECT查询是不会使用Next-Key Lock的,使用一致性锁定读才会使用Next-Key Lock。

4 丢失更新

丢失更新:一个事物的更新操作会被另一个事务的更新操作所覆盖。

我们知道在一个事务中对一个记录进行更新操作会对记录以及其他更高层次的页、表等对象加锁,因此多个事务间的更新操作不会相互影响。虽然数据库能够防止丢失更新的情况,但在应用层却很有可能出现这个问题,比如比较常见的查询、计算、更新操作,我们需要保证查询、计算和更新是一个原子操作,从应用层角度可以通过一系列手段进行控制,在数据库层面,我们就需要保证查询、计算和更新操作在同一个事务中,且多个事务之间是串行的,这可以通过SELECT * FOR UPDATE来实现。

(五)阻塞

因为锁的兼容性,有时一个事务需要等待另一个事务释放它所占用的资源,这就是阻塞。

在InnoDB中,可以通过innodb_lock_wait_timeout来控制事务的等待时间,默认是50秒,这个参数是动态的,可以在数据库运行时进行调整。

innodb_rollback_on_timeout用于设定是否在等待超时时对事务进行回滚操作(默认是OFF,即不回滚)。

(六)死锁

死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。

解决死锁问题最简单的方法是事务超时回滚,但这种方式可能会导致大事务的回滚,甚至在回滚时会占用很多undo log,这样一来超时回滚就不合适了。

因此,除了超时机制,目前大部分数据库都采用了wait-for graph(等待图)来进行死锁检测。wait-for graph要求数据库保存以下两种信息:

  1. 锁的信息链表
  2. 事务等待链表

例如:

从图中可以看到,事务t1在等待t2释放row1的X锁,事务t2在等待t1和t4释放row2的S锁,可以构造出等待图:

等待图中t1和t2之间存在回路,因此存在死锁,通常来讲,InnoDB会选择回滚undo量最小的事务。

注:等待图的死锁检测通常用深度优先算法实现。

特此声明:本系列博客为均为《MySql技术内幕 InnoDb存储引擎》读书笔记,存在错误还请指正

参考资料

《MySql技术内幕 InnoDb存储引擎》

猜你喜欢

转载自blog.csdn.net/greedystar/article/details/87864950
今日推荐