InnoDB中的锁

按照不同类型来介绍以下几种锁

共享/排它锁

  • 共享锁(S): 又称读锁,一个事务对数据加上S锁时,还允许其他事务再次对相同数据加S锁,但会阻止其他事务获得相同数据的排它锁,直到其他事务全部解锁.大致意思就是一个事务在对一个数据加入s锁的情况下,允许其他事务再次对这个数据加s锁并进行读操作,但不允许对这个数据进行写操作.lock in share mode

  • 排它锁(X):又称写锁,一个事务对数据加上X锁时,会阻止其他事务再次对相同数据加S和X锁,直到其他事务全部解锁.大致意思就是一个事务在对一个数据加入X锁的情况下,自己可以对这个数据进行读写操作,但不允许其他事务再次对这个数据进行加任何锁.select语句默认不会加任何锁类型,所以但是仍然可以进行普通无锁查询的.在一个语句for update

*注: innodb行锁是通过给索引上的索引项加锁来实现的,意味着只有通过索引条件检索的数据,innodb才会使用行锁否则将升级为表锁

查询行锁统计

show status like 'innodb_row_lock%'

image

参数详解,红色参数重要关注:如果等待次数高且时长不小的时候,就需要分析系统中为什么会有如此多的等待,然后根据分析结果进行相应的优化.

  • Innodb_row_lock_current_waits: 正在等待锁的数量
  • Innodb_row_lock_time: 从系统启动到目前一共锁定的总时间
  • Innodb_row_lock_time_avg:每次等待所花费的平均时间
  • Innodb_row_lock_time_max:从启动到现在等待最长的一个锁时间
  • Innodb_row_lock_waits: 从系统启动以来总共等待的次数

行锁优化建议

  • 尽可能让所有数据检索都通过缩影来完成,避免无索引行锁升级为表锁.
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能的减少检索条件,避免间隙锁
  • 尽量控制事务大小,减小锁定资源量和锁定资源的时间
  • 尽可能使用低的事务隔离级别

意向锁(ntention Locks)

  • 意向共享锁(IS): 当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁。
  • 意向排它锁(IX): 当一个事务试图对整个表进行加排它锁之前,首先需要获得这个表的意向排它锁。

这两中类型的锁共存的问题考虑这个例子:事务A锁住了表中的一行,让这一行只能读,不能写。之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。数据库要怎么判断这个冲突呢?
step1:判断表是否已被其他事务用表锁锁表
step2:判断表中的每一行是否已被行锁锁住。
注意step2,这样的判断方法效率实在不高,因为需要遍历整个表。
于是就有了意向锁。
在意向锁存在的情况下,事务A必须先申请"表"的意向共享锁,成功后再申请一行的行锁。
在意向锁存在的情况下,上面的判断可以改成
step1:不变,判断表是否已被其他事务用表锁锁表
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请"表"的写锁会被阻塞。

如果当事务A加锁成功之后就设置一个状态告诉后面的人,已经有人对表里的行加了一个排他锁了,你们不能对整个表加共享锁或排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态就知道自己是不是可以对表加锁,避免了对整个索引树的每个节点扫描是否加锁,而这个状态就是我们的意向锁。

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

意向锁和共享锁/排它锁之间的兼容关系 , ×:冲突, :兼容

X IX S IS
X × × × ×
IX × × ×
S × ×
IS ×

间隙锁(Gap Locks)

当我们使用SQL进行范围查询的时候, 并请求共享/排它锁时,innodb会给符合条件的已有数据的索引项进行加锁,对于在查询的条件范围内但并不存在的记录,叫做"间隙",innodb也会对这个间隙加锁,这种机制就是间隙锁.
*栗子: 假如test表中存在这样的记录,并有如下SQL:

id name
1 瑞文
2 狮子狗
3 小法
select * from test where id>0 for update;

innodb不仅会对id>0 (1,2,3)的这些记录加锁,对于其他大于100(这些数据并不存在)的间隙加锁

间隙锁的目的
  • 防止幻读,上例中如果不使用间隙锁,那么当其他事务插入数据时,本事务执行上述语句就会产生幻读.

当设计到间隙锁时,innodb这种锁机制最阻止符合条件范围内的数据插入(阻止获得排它锁),这往往会造成严重的锁等待,因此在实际开发中,对于插入较多的应用,尽量避免使用范围条件.

记录锁(Record Locks)

顾名思义,记录锁就是韦某行记录加锁,如:

select * form test where id = 1 for update;

id列必须为"唯一索引" 或者 "主键列" 且语句必须为精准匹配 = 不能为 < ,>, like等, 否则就会变为临键锁.

临键锁(Next-Key Locks)

  • record lock + gap lock, 左开右闭区间。
  • 默认情况下,innodb使用next-key locks来锁定记录。select … for update
  • 临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁,但当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。
  • Next-Key Lock在不同的场景中会退化:
场景 退化成的锁类型
使用唯一索引精确匹配(=),且记录存在 Record Lock
使用唯一索引精确匹配(=),且记录不存在 Gap Locks
使用唯一索引范围匹配(<,>) Next-Key Lock,锁上界,不锁下界
使用非唯一索引精确匹配(=) Next-Key Lock
假设有如下表:

MySql,InnoDB,Repeatable-Read:lock_example(id PK, age KEY, name)

id age name
1 10 Lee
3 24 Soraka
5 32 Zed
7 45 Tabn

该表中 age 列潜在的临键锁有:

(-∞, 10],

(10, 24],

(24, 32],

(32, 45],

(45, +∞],

在事务 A 中执行如下命令:

根据非唯一索引列 UPDATE 某条记录

UPDATE lock_example SET name = Vladimir WHERE age = 24;

或根据非唯一索引列 锁住某条记录

SELECT * FROM lock_example WHERE age = 24 FOR UPDATE;

不管执行了上述 SQL 中的哪一句,之后如果在事务 B 中执行以下命令,则该命令会被阻塞:

INSERT INTO table VALUES(100, 16, 'Ezreal');

很明显,事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了 (10, 24] 这个区间内的临键锁。

不仅如此,在执行以下 SQL 时,也会陷入阻塞等待:

INSERT INTO table VALUES(100, 30, 'Tom');

那最终我们就可以得知,在根据非唯一索引 对记录行进行 UPDATE \ FOR UPDATE \ LOCK IN SHARE MODE 操作时,InnoDB 会获取该记录行的临键锁 ,并同时获取该记录行下一个区间的间隙锁。

即事务 A在执行了上述的 SQL 后,最终被锁住的记录区间为 (10, 32]。

插入意向锁(Insert Intention Locks)

  • 插入意向锁是一种Gap锁,不是意向锁,在insert操作时产生。
  • 在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。
  • 假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。
  • 插入意向锁不会阻止任何锁,对于插+入的记录会持有一个记录锁。

自增锁(Auto-inc Locks)

  • 自增锁是一个特殊的表级锁,专门针对事务插入AUTO_INCREMENT的列
  • 一个事务往表中插入记录,其他事务必须等待当前事务插入完成,以便插入的是连续的主键值

总结:

  • 基于锁的属性分类:共享锁、排他锁。
  • 基于锁的粒度分类:表锁、行锁、记录锁、间隙锁、临键锁。
  • 基于锁的状态分类:意向共享锁、意向排它锁。
    待续...

参考:

https://zhuanlan.zhihu.com/p/127919778
https://blog.csdn.net/u010841296/article/details/84204701
https://www.zhihu.com/question/51513268/answer/127777478

posted @ 2021-07-16 23:42  朱思年  阅读(110)  评论(0编辑  收藏  举报