Mysql中的表锁和行锁

Mysql为了解决事物并发执行导致的一些列为题,引入了锁,在InnoDB存储引擎中,锁分为表锁行锁两类。

1. 共享锁和独占锁(S和X)

1.1 共享锁(S锁)

共享锁也叫S锁,S锁与S锁是兼容关系,不会被阻塞,S锁与X锁是不兼容的,会被阻塞。

加S锁读取数据:

SELECT ... LOCK IN SHARED MODE;

当在读取到的记录上加S锁,此时允许其他事物继续获取这些记录的S锁,但是不能获取X锁,会被阻塞,直到记录的S锁被释放为止。

1.2 独占锁(X锁)

独占锁也叫X锁,X锁和X锁是不兼容的,会被阻塞;X锁与S锁也是不兼容的,会被阻塞。

加S锁读取数据:

SELECT ... FOR UPDATE;

会为读取到的记录加X锁,此时不允许其他事务获取这些数据的S锁和X锁。如果其他事物想获取这些记录的S锁或X锁,会被阻塞,直到记录的X锁释放为止。

其实对于写操作来锁,基本是上都是加的X锁,例如UPDATEDELETE

DELETE在进行数据删除时,会先获取对应记录的X锁,然后执行删除操作,

UPDATE在进行数据修改时,在没有修改记录的主键的情况下,也是会先定位到对应的记录,然后获取记录的X锁,然后执行数据更新操作(这里的更新操作可能是将记录放到删除链上,再插入一条新记录;也可能是对修改列的数据进行修改。当被修改的列占用的存储空间发生变化时,前者的情况会发生);如果修改了主键,就会执行DELETE和INSERT操作。

INSERT进行数据插入时,一般是不会创建任何锁结构的,如果发生事物并发执行冲突时,会创建隐式锁进行处理。

2. 表锁

大概有以下几种:

  1. 表级S锁和X锁,分别表示表级别的共享锁和独占锁
  2. 表级意向锁,分为IS意向共享锁和IX意向独占锁
  3. 自增锁,还可以再分为两种锁
    1. AUTO-INC锁,在insert语句执行完成后会释放锁
    2. 轻量级锁,在生成值后会释放锁

2.1 表级S锁和X锁

一般是用不到表级S锁和X锁的,当然我们也可以手动获取表级S锁和X锁。

这里的READ对应的就是S锁,WRITE对应的是X锁。

LOCK TABLES 表名 READ;
LOCK TABLES 表名 WRITE;

2.2 意向锁

意向锁主要是用来表示一个加锁的状态的,目的是当其他事物加表锁的时候,可以快速知道当前表中有没有记录被加锁了。比如向表中的一条记录加S锁时,会先在表级别加一个IS锁,然后对记录加S锁。当下一个事务过来加表锁的时候,就可以通过判断表是否意向锁来确定表中是否有记录被加锁,从而避免了每次在加表锁的时候还要去遍历每条记录看是否加锁了。

2.3 自增锁

AUTO_INCREMENT修饰的列执行插入操作时,会使用自增锁,生成对应的列的值,然后将数据插入到表中。自增锁主要分为以下两种:

  1. AUTO-INC锁,在执行插入语句时,会加一个表级别的锁,然后为对应的列生成值,最后插入数据并释放自增锁。一般在无法预计插入数据条数时,会使用该方式进行自增列的值生成。
  2. 轻量级锁,在执行插入语句时,会获取自增锁,生成对应的列的值,然后释放锁,最后插入数据。一般在确定插入数据条数时,会使用该方法进行自增列值的生成。

3. 行级锁

行级锁是用来给表中的记录进行加锁的,与表级锁相比,锁的粒度更细,能提供更好的并发性能,但是会占用更多的资源。

3.1 记录锁(LOCK_REC_NOT_GAP)

记录锁锁定的是一条记录,有S锁和X锁之分,如果是给一条记录加了X记录锁,那么其他事物将无法获取该记录的X记录锁和S记录锁;如果给一条记录加了S记录锁,那么其他事物只能对该记录加S记录锁,不能加X记录锁。

   锁定id为5的这条记录加LOCK_REC_NOT_GAP
           ^
           |
           |
 _______________
| 1 | 3 | 5 | 8 |
|---------------|
| c | d | a | b | 
 ---------------

3.2 间隙锁(LOCK_GAP)

间隙锁锁定的是两个相邻记录中间的间隙,目的是锁定相邻记录的间隙,方式数据插入,Mysql中正是使用了这种方式进行加锁防止幻影行的插入。如下图所示,给ID为5的数据加间隙锁,会锁定ID为5的记录前面的间隙。间隙锁也分S锁和X锁,但是间隙锁的S锁和X锁的效果是一样的。

LOCK_GAP只是为了防止幻影行的插入,所以加了间隙锁,并不影响其他事物为该记录加记录锁和间隙锁。

 锁定id为5的这条记录加LOCK_GAP
         ^
         |
         |
 _______________
| 1 | 3 | 5 | 8 |
|---------------|
| c | d | a | b | 
 ---------------

3.3 Next-Key锁(LOCK_ORDINARY)

Next-Key锁可以看作是记录锁和间隙锁的合体,Next-Key锁会锁定记录前面的间隙和该记录

 锁定id为5的这条记录加LOCK_ORDINARY
         ^ ^
         | |
         | |
 _______________
| 1 | 3 | 5 | 8 |
|---------------|
| c | d | a | b | 
 ---------------

3.3 意向插入锁(LOCK_INSERT_INTENTION)

当记录插入时,要入位置被其他事物加了间隙锁(Next-Key锁也包含间隙锁),插入操作的事物会生成一个插入意向锁,然后进入等待状态等待间隙锁释放。

3.4 隐式锁

当一个事物进行记录插入操作,本身插入操作是不会创建任何锁结构的,但是此时遇到了另外一个事物在需要对该记录进行加锁(X锁或者S锁),此时就可能会出现脏读/写的情况,因为插入操作本身没有进行任何加锁,如果此时插入操作的事物没有提交,第二个事务如果锁定了该记录,进行了读或者写操作,就会出现脏读/写。

如果想避免脏读/写,正确的操作应该是先判断插入操作的事务是否提交了,如果提交了第二个事务就可能正常读/写该记录,如果没有提交,第二个事务就需要进入等待状态,此时第二个事务就会为插入操作的事物创建一个X锁,为当前事物本身(第二个事务)也创建一个锁结构,然后进入等待状态,此时第一个事物还在执行,第二个事务进入了阻塞状态,两个事物都对应一个锁结构。

4. 锁结构

  • 锁所在的事物信息 指向事物信息的指针
  • 索引信息 记录行级锁加锁的索引
  • 表/行锁信息
    • 表锁信息
    • 行锁信息
      • Space ID 记录所在的表空间
      • Page Number 记录所在的页号
      • n_bits 表示使用了多少个尾部的bit位
  • type_mode 记录的是锁相关的信息还有等待状态
    • lock_mode 锁模式,占用低4位
      • LOCK_IS 共享意向锁
      • LOCK_IX 独占意向锁
      • LOCK_S 共享锁
      • LOCK_X 独占锁
      • LOCK_AUTO_INC AUTO-INC锁
    • lock_type 锁类型
      • LOCK_TABLE 表锁
      • LOCK_REC 记录锁
    • rec_lock_type 行锁的具体类型
      • LOCK_ORDINARY next-key锁
      • LOCK_GAP 间隙锁
      • LOCK_REC_NOT_GAP 记录锁
      • LOCK_INSERT_INTENTION 意向锁
      • 其他信息
      • LOCK_WAIT 等待状态
  • 其他信息
  • 一堆bit位 如果是行锁,每一个bit位对应一条记录,0表示对应的记录没有加锁,1表示对应记录加锁了
        _________________  
       | 锁所在的事物信息  |
       |----------------|
       |     索引信息     |
       |----------------|
       |  表/行锁信息     |
       |----------------|
       |   type mod     |
       |--------------—-|
       |   其他信息      |
       |----------------|
       |   一些bit位     |
        ---------------- 
posted @ 2021-09-07 22:05  Godfunc  阅读(1401)  评论(0编辑  收藏  举报