Innodb存储引擎中锁的简析

mariadb

基本概念

innodb是MySQL/MariaDB的默认存储引擎,也是最广泛使用的存储引擎。几年前研究MySQL实现的时候,也看过一本关于innodb的书,但是理解并不到位。最近计划补充一下存储方面的知识,这里就从innodb入手研究一下。对于一个五级数据库引擎(实现事务功能),锁的设计是非常重要的,这篇博客的主题也就是学习innodb中锁。

innodb中有不少锁,这篇blog主要研究一下几个点,前两点是锁的模式,后两个知识点是锁的类型。这里我想强调一下,锁的模式和锁的类型是两个概念,千万不要搞混了。很多资料上,包括MySQL的官方文档上,都把这些知识点混在一起,给我带来了不少困惑。

  • Shared and Exclusive Locks:共享锁和互斥锁

  • Intention Locks:意向锁

两种类型的锁:

  • Record Locks
  • Gap Locks

1. Shared and Exclusive Locks

共享锁和互斥锁其实也不是什么新鲜的概念,它们和读写锁基本是一样的。这里讲到的互斥锁和共享锁是行级锁,通常使用X(exclusive)锁和S(shared)锁表示。在语义上也和读写锁一样;

假设事务T1获得了r行的共享锁,另一事务T2也准备在r行进行操作。如果T2请求共享锁,那么它将直接被允许;如果T2请求互斥锁,则它将被阻塞直到条件满足。

假设事务T1持有r行的互斥锁,另一事务T2对r行的所有的锁请求(包括S锁或者X锁)都将被阻塞。

表级别的共享锁和互斥锁也是类似的语义。

2. Intention Locks

前文所谈到的共享锁和互斥锁都是以行为单位的,但实际上innodb同时也是支持表级锁的。这样有个问题就是会出现过多的锁检查,所以innodb提出intention locks,意向锁是表级别的,相当于是更粗的粒度。intention locks有着如下的语义:

  • 在一个事务获得表t中某一行的共享锁之前,它必须先获得表t的IS(意向共享锁)锁
  • 在一个事务获得表t中某一行的互斥锁之前,它必须先获得表t的IX(意向互斥锁)锁

下表是各种锁之间的兼容性示意,值得注意的是意向锁并不和行级锁冲突,表里的S和X都是指的表一级的锁。

image

3. Record locks

Record locks是innodb中很重要的一种锁,record locks是通过对索引加锁实现的。record locks是行级锁,一个record lock可以锁定一行。举例来说,“SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;”这样一条SQL执行时就需要对c1=10的行加record locks。这里有一点需要注意,当表没有索引时,innodb为表定义一个隐藏索引,然后record lock就作用在隐藏索引上了。

4. Gap locks

Gap locks顾名思义就是锁住一个间隔区域,innodb存储引擎使用B+树,而B+树本身就使用了区间来维护树形结构。Gap locks也是作用在索引上的,gap locks主要工作在read repeatable事务隔离级别下。gap locks可以用来防止幻读的产生。

相关数据结构

前文简单地对innodb锁进行了解释,接下来从一些代码片段来看看innodb锁相关的实现。

首先是锁的模式(storage/innobase/include/lock0types.h):

/* Basic lock modes */
enum lock_mode {
 LOCK_IS = 0, /* intention shared */
 LOCK_IX, /* intention exclusive */
 LOCK_S, /* shared */
 LOCK_X, /* exclusive */
 LOCK_AUTO_INC, /* locks the auto-inc counter of a table
 in an exclusive mode */
 LOCK_NONE, /* this is used elsewhere to note consistent read */
 LOCK_NUM = LOCK_NONE, /* number of lock modes */
 LOCK_NONE_UNSET = 255
};

然后是锁的类型(storage/innobase/include/lock0lock.h):

#define LOCK_TABLE 16 /*!< table lock */
#define LOCK_REC 32 /*!< record lock */
...
#define LOCK_WAIT 256 /*!< ... it is just waiting for its
    turn in the wait queue */
#define LOCK_ORDINARY 0 /*!< this flag denotes an ordinary
    next-key lock ... */
#define LOCK_GAP 512 /*!< when this bit is set, it means that the
    lock holds only on the gap before the record;...locks of this       
    type are created when records are removed from the index chain  of records */

#define LOCK_REC_NOT_GAP 1024 /*!< this bit means that the lock is only
    on the index record and does NOT block inserts to the gap before the
    index record;  ... */
#define LOCK_INSERT_INTENTION 2048 /*!< this bit is set when we place a
    waiting gap type record lock request in order to let an insert of an
    index record to wait until there are no conflicting locks by other
    transactions on the gap;  note that this flag remains set when the
    waiting lock is granted, or if the lock is inherited to a neighboring
    record */
#define LOCK_PREDICATE 8192 /*!< Predicate lock */
#define LOCK_PRDT_PAGE 16384 /*!< Page lock */

然后是锁的通用表示,是一个结构体struct lock_t:

struct lock_t {
    trx_t* trx; /*!< transaction owning the lock */
    UT_LIST_NODE_T(lock_t) trx_locks; /*!< list of the locks ...     */
    dict_index_t* index; /*!< index for a record lock  */
    lock_t* hash; /*!< hash chain node for a rec. lock  */
    union {
        lock_table_t tab_lock; /*!< table lock */
        lock_rec_t rec_lock; /*!< record lock */
    } un_member; /*!< lock details */
    ib_uint32_t type_mode; /*!< lock type, mode, LOCK_GAP or
    LOCK_REC_NOT_GAP,
    LOCK_INSERT_INTENTION,
    wait flag, ORed */
};

其中的联合体un_member,说明它就是各种锁的包装结构体。其中tab_lock字段表示表锁,rec_lock表示行级锁。另外一个字段type_mode,它通过位运算表示这个锁的类型和模式。而表锁和行锁的定义如下:

/** A table lock */
struct lock_table_t {
 dict_table_t* table; /*!< database table in dictionary cache */
 UT_LIST_NODE_T( lock_t) locks; /*!< list of locks on the same
 table */
};

/** Record lock for a page */
struct lock_rec_t {
 ib_uint32_t space; /*!< space id */
 ib_uint32_t page_no; /*!< page number */
 ib_uint32_t n_bits; /*!< number of bits in the lock
    bitmap;  NOTE: the lock bitmap is
    placed immediately after the
    lock struct */
};

这里就不对它们的结构做具体解释了,其中record locks使用了位图来记录锁以节省存储空间。

 

参考资料:

【1】https://www.slideshare.net/billkarwin/innodb-locking-explained-with-stick-figures?from_action=save

【2】https://www.slideshare.net/valeriikravchuk1/understanding-innodb-locks-and-deadlocks

发表评论