数据库原理篇(3)

InnoDB中的锁

Posted by Jason Lee on 2019-10-23

InnoDB 介绍

InnoDB 中的锁

你可能听过各种各样的 InnoDB 的数据库锁,Gap 锁,共享锁,排它锁,读锁,写锁等等。但是 InnoDB 的标准实现的锁只有 2 类,一种是行级锁,一种是意向锁

InnoDB的行级锁:

  • 共享锁(读锁 S Lock),允许事务读一行数据。
  • 排它锁(写锁 X Lock),允许事务删除一行数据或者更新一行数据。
    行级锁中,除了 S 和 S 兼容,其他都不兼容。

InnoDB 支持两种意向锁(即为表级别的锁):

  • 意向共享锁(读锁 IS Lock),事务想要获取一张表的几行数据的共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
  • 意向排他锁(写锁 IX Lock),事务想要获取一张表中几行数据的排它锁,事务在给一个数据行加排它锁前必须先取得该表的 IX 锁。

首先解释一下意向锁,意向锁为了表明某个事务正在锁定一行或者将要锁定一行数据。
首先申请意向锁的动作是 InnoDB 完成的,怎么理解意向锁呢?

例如:事务 A 要对一行记录 R 进行上 X 锁,那么 InnoDB 会先申请表的 IX 锁,再锁定记录 R 的 X 锁。在事务 A 完成之前,事务 B 想要来个全表操作,此时直接在表级别的 IX 就告诉事务 B 需要等待而不需要在表上判断每一行是否有锁。
意向排它锁存在的价值在于节约 InnoDB 对于锁的定位和处理性能。另外注意了,除了全表扫描以外意向锁都不会阻塞。

意向锁的存在意义

如果另一个任务试图在该表级别上应用共享或排它锁,则受到由第一个任务控制的表级别意向锁的阻塞。第二个任务在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。

假设 我们有一张users表

id name
1 zhangsan
2 lisi

当事务A 想要更新id=2的user 的时候,会做以下操作

  1. DB引擎给表增加一个意向排它锁
  2. 事务A给id=2的行增加一个排它锁

这里的排他 / 共享锁指的都是表锁!!!意向锁不会与行级的共享 / 排他锁互斥!!!

此时若有事务B想获取 User表的共享锁(注意,这里是表级别的锁) 这个时候,因为是要锁表,所以意味着表中的所有数据都不能修改,但是此时A 已经对2 加了锁,所有如果没有意向锁,事务B 必须在获取表级别的共享锁的时候,检测每一行是否有排他锁。

但是一旦有了意向共享锁,所以事务B 因此不用每一行都扫描。

因此此时事务 B 检测事务 A 持有 users 表的意向排他锁,就可以得知事务 A 必然持有该表中某些数据行的排他锁,那么事务 B 对 users 表的加锁请求就会被排斥(阻塞),而无需去检测表中的每一行数据是否存在排他锁。

我们可以根据下表来看一下各个锁的兼容性

锁的兼容互斥性

看一下意向锁的排斥性

意向共享锁(IS) 意向排他锁(IX
意向共享锁(IS) 兼容 兼容
意向排他锁(IX) 兼容 兼容

意义: 意向锁不会跟对意向锁产生排斥,这设计到表的并发。
举个例子:还是刚才的User 表
事务A 要对id = 2 的表进行更新,那么事务A 会产生两个效果

  1. 表级别的意向排它锁
  2. 行级别的排他锁。
    此时B 事务想更新 id = 1 的行,
  3. 首要获取User 的意向排它锁,
  4. 发现User 表已经有意向排它锁
  5. 但是意向排他锁是互相兼容的,因此,事务B 加锁成功
  6. id = 1的数据行上不存在任何排他锁,最终事务 B 成功获取到了该数据行上的排他锁。

意向锁对共享和排他锁的互斥性

意向共享锁(IS) 意向排他锁(IX)
共享锁(IS) 兼容 互斥
排他锁(IX) 互斥 互斥

锁的算法

InnoDB 有 3 种行锁的算法:

  • Record Lock: 单个行记录上的锁。
  • Gap Lock: 间隙锁,锁定一个范围,而非记录本身。
  • Next-Key Lock:结合 Gap Lock 和 Record Lock,锁定一个范围,并且锁定记录本身。主要解决的问题是 RR 隔离级别下的幻读。

Next-Key LockGap Lock 的区别就是:会首先对索引(如果我们的查询语句命中索引记录的话)记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
如果没有命中索引记录,则直接退化成 Gap Lock

Gap Lock 和 Next-Key Lock

  • 产生间隙锁的条件(RR事务隔离级别下;):
    • 使用普通索引锁定;
    • 使用多列唯一索引;
    • 使用唯一索引锁定多行记录。
    • 不使用索引则加全局锁
  • 举例说明,假若我们有如下表
id(主键) v1(索引) v2(无索引)
1 1 0
2 3 1
3 4 2
5 5 9
7 7 4
10 9 5

间隙锁(Gap Lock)一般是针对非唯一索引而言的,test表中的v1(非唯一索引)字段值可以划分的区间为:
-∞,1)(1,3)(3,4)(4,5)(5,7)(7,9)(9, +∞)

假如要更新v1=7的数据行,锁定的区间是(5,7)和(7,9)。同时找到,v1=7的数据行的主键索引和非唯一索引,对key加上锁。

  • 插入锁

    事物1 事物2
    UPDATE test set v2=10 where v1=7(加锁范围(5,9))
    insert into test values(15,6,3)(阻塞))
    insert into test values(9,9,3)(阻塞))
    insert into test values(11,9,3)(成功))
    insert into test values(4,5,3)(成功))

    看一下 插入图

  • 更新锁

    事物1 事物2
    UPDATE test set v2=10 where v1=7(加锁范围(5,9))
    update test set v1=6 where v1=5(阻塞))
    update test set v1=9 where v1=7(阻塞))
    update test set v1=10 where v1=9(成功))

    v1 = 6 相当于在 5 7 范围内加入

锁选择

  • 1)、如果更新条件没有走索引,此时会进行全表扫描,扫表的时候,要阻止其
    他任何的更新操作,所以上升为表锁。
  • 2)如果更新条件为索引字段,但是并非唯一索引(包括主键索引)那么此时
    更新会使用Next-Key Lock。使用Next-Key Lock的原因:
    a)、首先要保证在符合条件的记录上加上排他锁,会锁定当前非唯一索引和对应的主键索引的值;
    b)、还要保证锁定的区间不能插入新的数据。
  • 3)、如果更新条件为唯一索引,则使用Record Lock(记录锁)。

锁应用

  • 在不通过索引条件查询时,InnoDB 会锁定表中的所有记录。所以,如果考虑性能,WHERE语句中的条件查询的字段都应该加上索引。
  • InnoDB通过索引来实现行锁,而不是通过锁住记录。因此,当操作的两条不同记录拥有相同的索引时,也会因为行锁被锁而发生等待。
  • 由于InnoDB的索引机制,数据库操作使用了主键索引,InnoDB会锁住主键索引;使用非主键索引时,InnoDB会先锁住非主键索引,再锁定主键索引。
  • 当查询的索引是唯一索引(不存在两个数据行具有完全相同的键值)时,InnoDB存储引擎会将Next-Key Lock降级为Record Lock,即只锁住索引本身,而不是范围。
  • InnoDB对于辅助索引有特殊的处理,不仅会锁住辅助索引值所在的范围,还会将其下一键值加上Gap LOCK。
  • InnoDB使用Next-Key Lock机制来避免Phantom Problem(幻读问题)。

参考



支付宝打赏 微信打赏

赞赏一下