InnoDB存储引擎中的锁

InnoDB存储引擎中的锁

   概要

   人们认为行级锁总会增加开销。实际上,只有当实现本身会增加开销时,行级锁才会增加开销。InnoDB存储引擎不需要锁升级,因为一个锁和多个锁的开销是相同的。

   一、什么是锁 ?

   锁是数据库系统区别于文件系统 的一个关键特性。锁机制用于管理对共享资源的并发访问。

   二、锁类型

   在 InnoDB 中实现了两个标准的行级锁,可以简单的看为两个读写锁:

   S 共享锁:又叫读锁,其他事务可以继续加共享锁,但是不能继续加排他锁。

   X 排他锁:又叫写锁,一旦加了写锁之后,其他事务就不能加锁了。

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

   如下图:

    

   说明:从上图中可以看出X锁与任何的锁都不兼容,而S锁仅和S锁兼容。需要特别注意的是,S和X锁都是行锁,兼容是指对同一记录 (row) 锁的兼容性情况。

   三、意向锁 (Intention Lock)

   MySQL 自身就提供了表锁的能力,行锁是 InnoDB 存储引擎提供的,MySQL 本身并不提供行级锁的能力。

   锁共存问题

   既有表锁又有行锁,我们来考虑下这两种类型的锁共存的问题。看下面这个例子:

   事务 A 加了行级读锁,锁住了表中的一行,让这一行只能读,不能写。之后,事务 B 尝试申请整个表的写锁。

   如下图:

    

   如果事务 B 申请成功,那么理论上它就能修改表中的任意一行,这与 A 持有的行级读锁是冲突的。

   数据库需要避免这种冲突,就势必要让 B 的申请被阻塞,直到 A 释放行级读锁。

   那数据库要怎么判断这个冲突呢?

  • 步骤 1:判断表是否已被其他事务用表级锁锁住了整张表
  • 步骤 2:判断表中的每一行是否已被行级锁锁住

   看起来没有什么困难的,但请注意步骤 2,判断表中的每一行,各位,如何判断?

   显然,需要遍历!遍历表中的每一行。但是,这样的判断方法效率实在太过于低下了。于是就有了意向锁!

   1. 什么是意向锁

   InnoDB存储引擎支持多粒度(granular)锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,

称之为意向锁(Intention Lock)。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度(fine granularity)上进行加锁。

   InnoDB存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁。设计目的主要是为了在一个事务中揭示下一行将被请求的锁类型。

   其支持两种意向锁为:

   1)意向共享锁 (Intention Shared Lock, IS Lock):表达一个事务想要获取一张表中某几行的共享锁(行级读锁)时,InnoDB 存储引擎会自动地先获取该表的意向读锁(表级锁)。

   2)意向排他锁(Intention Exclusive Lock, IX Lock):表达一个事务想要获取一张表中某几行的排他锁(行级写锁)时,InnoDB 存储引擎会自动地先获取该表的意向写锁(表级锁)。

   注意这里的自动:申请意向锁的动作是数据库完成的,就是说,事务 A 申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。

   在意向锁存在的情况下,事务 A 如果想申请行级读锁,就必须先申请该表的意向读锁,申请成功后才能继续申请某行记录的行级读锁。

   2. 意向锁如何解决最上面提到的锁并存的问题?

   在意向锁存在的情况下,上面的判断可以改成:

  • 步骤 1(不变):判断表是否已被其他事务用表级锁锁住了整张表
  • 步骤 2:发现表上有意向读锁(说明表中有些行被行级读锁锁住了),意向读锁和表级写锁互斥,因此,事务 2 申请表的写锁会被阻塞。

   也就是说原先步骤 2 的遍历表中每一行的操作,简化成了判断下整张表上有无表级意向锁就行了,效率大幅提升。

   如下图:

    

   3. 意向锁的工作原理

   1)当一个事务尝试对表中的某些行加锁时,它首先会在表级别加一个意向锁(IS 或 IX)。

   2)其他事务在尝试加表级锁(如表级共享锁 S 或表级排他锁 X)时,会检查表级的意向锁情况,决定是否可以加锁。

   4. 意向锁的具体案例

   假设有一个事务 T1 想要在表 orders 中对某些行加排他锁(X),而另一个事务 T2 想要对整个表加共享锁(S)。

  • T1 先对表 orders 加意向排他锁(IX),然后对某些行加排他锁(X)。
  • T2 尝试对表 orders 加共享锁(S),此时发现表上已经有 IX 锁,因此不能加 S 锁,T2 需要等待 T1 释放锁。

    通过这个机制,InnoDB 可以在不同粒度的锁之间进行协调,确保锁定操作的高效性和一致性。

   5. InnoDB存储引擎中锁的兼容性

   如下图:

    兼容性:是指事务 A 获得一个某行某种锁之后,事务 B 同样的在这个行上尝试获取某种锁,如果能立即获取,则称锁兼容,反之叫冲突。

    纵轴是代表已有的锁,横轴是代表尝试获取的锁。

    6. 意向锁的作用

    1)提高锁定效率

    通过在表级别设置意向锁,可以避免每次在行级别加锁时都要检查整个表的锁情况。这种机制提高了锁定效率,特别是在大表上的操作。    

    2)保证多粒度锁定一致性

    意向锁允许在表级别和行级别之间进行协调,从而确保锁定机制的一致性和正确性。例如,在表级别加排他锁前,意向锁会确保表中没有其他事务持有行级锁。 

    3)避免死锁

    意向锁通过提前声明锁定意图,帮助 InnoDB 预防和检测死锁情况,提高系统的并发处理能力和可靠性。

    7. 总结

    1)InnoDB 支持多粒度锁,特定场景下,行级锁可以与表级锁共存。

    2)IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。

    3)意向锁在保证并发性的前提下,实现了行锁和表锁共存且满足事务隔离性的要求。 

    四、锁的算法

    InnoDB存储引擎有3种行锁的算法,其分别是:

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

    具体在下面分别介绍:

    1. 记录锁 (Record Lock)

    Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定。

    需要注意以下几点:

    1)记录锁是锁住记录的,锁住的是索引记录,而不是我们真正的数据记录:

    2)如果锁的是非主键索引,会在自己的索引上面加锁之后然后再去主键上面加锁锁住。

    3)如果表上没有索引(包括没有主键),则会使用隐藏的主键索引进行加锁。

    4)如果要锁的没有索引,则会进行全表记录加锁。

    2. 间隙锁 (Gap Lock)

    间隙锁顾名思义锁间隙,不锁记录。锁间隙的意思就是锁定某一个范围,间隙锁又叫 gap 锁,其不会阻塞其他的 gap 锁,但是会阻塞插入间隙锁,这也是用来防止幻读的关键。

    如下图:

 

     

 

    3. Next-Key Lock

    Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法。

    在InnoDB 默认的事务隔离级别下,即REPEATABLEREAD下,InnoDB存储引擎采用Next-KeyLocking这种锁定算法。例如一个索引有10,11,13和20这四个值,那么该索引可能被Next-Key Locking的区间为:

    (-∞,10] 、 (10,11] 、 (11,13] 、(13,20] 、(20,+∞)

    当查询的索引含有唯一属性(unique字段)时,InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。

 

    参考链接:

    https://www.cnblogs.com/wade-luffy/p/9689975.html

    https://www.51cto.com/article/743293.html

 

posted @ 2024-07-06 12:21  欢乐豆123  阅读(3)  评论(0编辑  收藏  举报