天天看点

mysql锁机制之行锁(四)

前言

顾名思义,行锁就是一锁锁一行或者多行记录,mysql的行锁是基于索引加载的,所以行锁是要加在索引响应的行上,即命中索引,如下图所示:

InnoDB 支持​

​多粒度锁(multiple granularity locking)​

​​,它允许​

​行级锁​

​​与​

​表级锁​

​共存,而意向锁就是其中的一种​

​表锁​

​。

上面我们找到行锁是命中索引,一锁锁的是一张表的一条记录或者是多条记录,记录锁是在行锁上衍生的锁,我们来看看你记录锁的特征:

记录锁:记录锁锁的是表中的某一条记录,记录锁的出现条件必须是精准命中索引并且索引是唯一索引,如主键id,就像我们上面描述行锁时使用的sql语句图,在这里就挺适用的。

间隙锁又称之为区间锁,每次锁定都是锁定一个区间,隶属行锁。既然间隙锁隶属行锁,那么,间隙锁的触发条件必然是命中索引的,当我们查询数据用范围查询而不是相等条件查询时,查询条件命中索引,并且没有查询到符合条件的记录,此时就会将查询条件中的范围数据进行锁定(即使是范围库中不存在的数据也会被锁定),我们通过代码演示一下:

首先,我们打开两个窗口,在窗口A中我们根据id做一个范围更改操作,不提交事务,然后在范围B中插入一条记录,该记录的id值位于窗口A中的条件范围内,我们看看运行效果:

意向锁(Intention Locks)

需要强调一下,意向锁是一种​

​不与行级锁冲突表级锁​

​,这一点非常重要。意向锁分为两种:

  • 意向共享锁(intention shared lock, IS):事务有意向对表中的某些行加共享锁(S锁)
-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table ... LOCK IN SHARE MODE;
复制代码      
  • 意向排他锁(intention exclusive lock, IX):事务有意向对表中的某些行加排他锁(X锁)
-- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column FROM table ... FOR UPDATE;
复制代码      

即:​

​意向锁是有数据引擎自己维护的,用户无法手动操作意向锁​

​,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。

意向锁要解决的问题

我们先来看一下百度百科上对意向锁存在意义的描述:

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

设想这样一张 ​

​users​

​ 表: MySql,InnoDB,Repeatable-Read:users(id PK,name)

id name
1 ROADHOG
2 Reinhardt
3 Tracer
4 Genji
5 Hanzo
6 Mccree

事务 A 获取了某一行的排他锁,并未提交:

SELECT * FROM users WHERE id = 6 FOR UPDATE;
复制代码      

事务 B 想要获取 ​

​users​

​ 表的表锁:

LOCK TABLES users READ;
复制代码      

因为共享锁与排他锁​

​互斥​

​​,所以事务 B 在视图对 ​

​users​

​ 表加共享锁的时候,必须保证:

  • 当前没有其他事务持有 users 表的排他锁。
  • 当前没有其他事务持有 users 表中任意一行的排他锁 。

为了检测是否满足第二个条件,事务 B 必须在确保 ​

​users​

​表不存在任何排他锁的前提下,去检测表中的每一行是否存在排他锁。很明显这是一个效率很差的做法,但是有了意向锁之后,情况就不一样了:

意向锁的兼容互斥性

意向锁是怎么解决这个问题的呢?首先,我们需要知道意向锁之间的兼容互斥性:

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

即意向锁之间是互相兼容的,emmm......那你存在的意义是啥?

虽然意向锁和自家兄弟互相兼容,但是它会与普通的排他 / 共享锁互斥:

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

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

现在我们回到刚才 ​

​users​

​ 表的例子:

​事务 A​

​ 获取了某一行的排他锁,并未提交:

SELECT * FROM users WHERE id = 6 FOR UPDATE;
复制代码      

此时 ​

​users​

​​ 表存在两把锁:​

​users​

​ 表上的意向排他锁与 id 为 6 的数据行上的排他锁。

事务 B 想要获取 users 表的共享锁:

LOCK TABLES users READ;
复制代码      

此时​

​事务 B​

​​ 检测事务 A 持有 ​

​users​

​ 表的意向排他锁,就可以得知​

​事务 A​

​ 必然持有该表中某些数据行的排他锁,那么​

​事务 B​

​​ 对 ​

​users​

​ 表的加锁请求就会被排斥(阻塞),而无需去检测表中的每一行数据是否存在排他锁。

意向锁的并发性

这就牵扯到我前面多次强调的一件事情:

意向锁不会与行级的共享 / 排他锁互斥!!!

意向锁不会与行级的共享 / 排他锁互斥!!!

意向锁不会与行级的共享 / 排他锁互斥!!!

重要的话要加粗说三遍,正因为如此,意向锁并不会影响到多个事务对不同数据行加排他锁时的并发性(不然我们直接用普通的表锁就行了)。

最后我们扩展一下上面 users 表的例子来概括一下意向锁的作用(一条数据从被锁定到被释放的过程中,可能存在多种不同锁,但是这里我们只着重表现意向锁):

id name
1 ROADHOG
2 Reinhardt
3 Tracer
4 Genji
5 Hanzo
6 Mccree

​事务 A​

​ 先获取了某一行的排他锁,并未提交:

SELECT * FROM users WHERE id = 6 FOR UPDATE;
复制代码      
  1. ​事务 A​

    ​​ 获取了​

    ​users​

    ​ 表上的意向排他锁。
  2. ​事务 A​

    ​ 获取了 id 为 6 的数据行上的排他锁。
LOCK TABLES users READ;
复制代码      
  1. ​事务 B​

    ​​ 检测到​

    ​事务 A​

    ​​ 持有​

    ​users​

    ​ 表的意向排他锁。
  2. ​事务 B​

    ​​ 对​

    ​users​

    ​ 表的加锁请求被阻塞(排斥)。
SELECT * FROM users WHERE id = 5 FOR UPDATE;
复制代码      
  1. ​事务 C​

    ​​ 申请​

    ​users​

    ​ 表的意向排他锁。
  2. ​事务 C​

    ​​ 检测到​

    ​事务 A​

    ​​ 持有​

    ​users​

    ​ 表的意向排他锁。
  3. 因为意向锁之间并不互斥,所以​

    ​事务 C​

    ​​ 获取到了​

    ​users​

    ​ 表的意向排他锁。
  4. 因为id 为 5 的数据行上不存在任何排他锁,最终​

    ​事务 C​

    ​ 成功获取到了该数据行上的排他锁。

总结

  1. InnoDB 支持​

    ​多粒度锁​

    ​,特定场景下,行级锁可以与表级锁共存。
  2. 意向锁之间互不排斥,但除了 IS 与 S 兼容外,​

    ​意向锁会与 共享锁 / 排他锁 互斥​

    ​。
  3. IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。
  4. 意向锁在保证并发性的前提下,实现了​

    ​行锁和表锁共存​

    ​​且​

    ​满足事务隔离性​

    ​的要求。