阅读 1216

数据库锁机制

数据库和MVCC多版本并发控制基本介绍可以参考之前的文章 数据库和MVCC多版本并发控制

本文所有内容基于MySQL/InnoDB引擎。

并发事务解决方案

脏读、不可重复读和幻读都是数据库读一致性问题,需要由数据库提供一定的事务隔离机制来解决。

(1)锁机制

解决写-写冲突问题。在读取数据前,对其加锁,防止其它事务对该数据进行修改。

  • 悲观锁

    往往依靠数据库提供的锁机制。

  • 乐观锁

    大多是基于数据版本记录机制来实现。

(2)MVCC多版本并发控制

解决读-写冲突问题。不用加锁,通过一定机制生成一个数据请求时间点时的一致性数据快照, 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。这样在读操作的时候不需要阻塞写操作,写操作时不需要阻塞读操作。

MySQL/InnoDB锁机制

加锁方式

  • 两段锁

    加锁/解锁是两个不相交的阶段,加锁阶段:只加锁,不解锁。解锁阶段:只解锁,不加锁。

锁类型

锁

锁粒度

按照锁粒度维度来看,MySQL数据库根据不同的存储引擎可以有表级锁、页级锁和行级锁。

表级锁

MySQL中锁定粒度最大的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率越高,并发度最低,MyISAM和InnoDB引擎都支持表级锁。

行级锁

Mysql中锁定粒度最小的一种锁,只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

虽然使用行级锁具有粒度小、并发高等特点,但是表级锁在某些场景下也是必需的。

  • 事务更新大表中的大部分数据时直接使用表级锁效率更高,避免频繁加行级锁。
  • 事务比较复杂,使用行级锁很可能会导致死锁导致回滚。

锁的兼容性

按照是否兼容来分类,表级锁和行级锁可以再细分为共享锁和排它锁。

共享锁

共享锁(Share Locks,简记为S锁)又称为读锁。其它事务可以并发地读取数据,可以再加共享锁,但任何事务都不能获取数据上的排它锁,直至已经释放所有共享锁。

排它锁

排它锁(Exclusive lock,简记为X锁)又称为写锁。若事务对数据对象加上了排它锁,则只允许该事务对数据对象进行读取和修改,其它事务不能再对数据对象加任何类型的锁,直到该事务释放对象上的排它锁。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。

意向锁

为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁

  • 意向共享锁(IS)

    表示事务准备给数据行加入共享锁,事务在给一个数据行加共享锁之前必须先取得该表的IS锁。

  • 意向排它锁(IX)

    表示事务准备给数据行加入排它锁,事务在给一个数据行加排它锁之前必须先取得该表的IX锁。

意向锁的作用

MySQL中表锁和行锁共存,若不引入意向锁,该如何判断是否锁冲突呢?

假设事务T要对表T1加X锁,那就必须要判断T1表下每一个数据行是否加了S锁或者X锁。这样做的效率会非常低,需要对整个表进行遍历。在引入意向锁之后情况变得简单了。

假设事务T要对表T1加X锁,在这之前假设已经有事务A对数据行R加了S锁,那么此时表上已经有IS锁了(事务在给一个数据行加S锁之前必须先取得该表的IS锁)。由于X锁和IS锁冲突,所以事务T需要等待锁操作完成。这样就省去了遍历的操作,提高了冲突判断效率。

注意事项
  1. 意向锁是表锁,表示的是一种意向,仅仅表示事务正在读或写某一行记录,在真正加行锁时才会判断是否冲突。意向锁是InnoDB自动加的,不需要用户干预。
  2. IX和IS是表锁,不会与行锁发生冲突,只会与表锁发生冲突
兼容情况

InnoDB的锁兼容情况如下所示。

锁算法

InnoDB主要实现了三种行锁算法。

  • Record Lock

    记录锁,锁定一个行记录

  • Gap Lock

    间隙锁,锁定一个区间

  • Next-Key Lock

    记录锁+间隙锁,锁定行记录和区间

Next-Key Lock

Gap Lock和Next-Key Lock是为了解决幻读问题。

我们知道MVCC里面Repeated Read级别是快照读,read view是在执行事务中第一条select语句的瞬间创建,后续所有的select都是复用这个对象,所以能保证每次读取的一致性。可是这并不能确保就不会出现幻读问题了,仍然可能在执行insert/update时遇到幻读现象,因为SELECT 不加锁的快照读行为是无法限制其他事务对新增重合范围的数据的插入的。可能会出现这样的情况,select出了2条记录,update的时候却返回了3个成功结果。

InnoDB通过间隙锁来锁定区间间隔,避免其它事务在这个区间内插入其它数据导致出现幻读现象。

在MySQL的规范里面RR事务级别是可能出现幻读的,InnoDB通过间隙锁避免了这种情况。这个实现和规范有所差别。另外,Next-Key Lock是Repeated Read级别才会有的,在Read Committed级别并不存在。

示例

假设表T1(id private key),一共有3条记录1、3、5,同时有两个事务在进行。

事务A在T2时刻查询的结果为5,由于使用select … for update语句,这会在区间(3,+∞)这个范围内加上Gap Lock,因此在这个区间内的插入都是不被允许的。所以T6时刻查询结果也是5,避免了幻读现象。事务B在T4时刻想插入4,这个属于(3,+∞)区间,因此写入会被阻塞,直到事务A结束。

参考资料

InnoDB 的意向锁有什么作用?

MySQL 加锁处理分析

Mysql锁机制简单了解一下

MySQL InnoDB锁机制全面解析分享

一文了解Mysql的InnoDB存储引擎中的锁

关注下面的标签,发现更多相似文章
评论