0%

InnoDB 锁类型详解

锁是用于管理不同事务对共享资源的并发访问

表锁和行锁的区别:

在加锁效率、锁定粒度以及冲突概率上,表锁肯定是大于行锁的

但是在并发性能上,表锁远低于行锁。

表锁是锁定了整个表,在加锁期间,无论读写,这个表的数据都是锁定的,相反行锁只是锁定了这个表中的一条数据,其他数据仍然可以操作,这就可很好的提高了数据库的并发性能。

Mysql Innodb 锁类型

  • 共享锁 Shared Locks (简称 S 锁,属于行锁)
  • 排他锁 Exclusive Locks(简称 X 锁,属于行锁)
  • 意向共享锁 Intention Shared Locks (简称 IS 锁,属于表锁)
  • 意向排他锁 Intention Exclusive Locks (简称 IX 锁,属于表锁)
  • 自增锁 AUTO-INC Locks

共享锁(S)与排它锁 (X)

共享锁

又称之为 读锁,简称 s 锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据库,但是只能读不能修改;

加锁方式:

select * from users where id = 1 lock in share mode;

释放方式:

rollback/commit;

举例:

当手动为select语句加上共享锁之后,在右边的会话中我们对该条数据执行update 操作 ,会发现一直卡住,这就是说,加了共享锁的数据,只能被其他事务读取,但是不能被修改

img

当我们 commit/rollback结束掉左边会话框的事务时,会发现右边会话框的update操作可以正常进行了

img

但是我们要注意一点,哪就是共享锁是不影响其他事物读取数据的,如下举例:

img

排它锁

又称为写锁,简称 X 锁,排它锁不能与其他锁并存,如一个事务获取了一个数据行的排它锁,其他事务就不能再获取改行的锁(包括共享锁和排它锁),只有当前获取了排它锁的事务可以对数据进行读取和修改(此时其他事务要读取数据可从快照获取)

加锁方式:

delete update insert 默认加排他锁

select * from users where id = 1 for update;

释放方式:

rollback/commit;

举例:

另一事务获取共享锁,排他锁,都会锁住

img

img

InnoDB 行锁到底锁的是什么?

我们首先来看如下一个例子:

img

发现在事务1中对id=1的数据行做了更新操作,但是事务未提交之前,事务2去再去更新这条数据会卡住,也就是被锁住了。

接下来我们在事务1 未做任何改变,保持事务未提交状态的情况下去更新id = 2 的数据行

img

结果显而易见,更新数据成功了。

综上所述:

InnoDB的行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件进行数据检索,Innodb才使用行级锁。否则,将使用表锁(锁住索引的所有记录)。

借此我们是不是能联想到,如果我们的删除/修改语句是没有命中索引的,哪么,则会锁住整个表,这在性能上的影响还是挺大的。

意向共享锁(IS)和意向排他锁(IX)

意向共享锁

表示事务准备给数据行加入共享锁,也就是说一个数据行在加共享锁之前必须先取得该表的IS锁。

意向排他锁

表示事务准备给数据行加入排它锁,也就是说一个数据行加排它锁之前必须先取得该表的IX锁。

  • 意向锁是InnoDB数据操作之前自动加的,不需要用户干预

  • 意向锁是表级锁

关于这两个锁的实际演示例子本文鉴于篇幅便不再赘述,感兴趣的可以根据上边描述的共享锁和排他锁演示过程自己体验一遍.

这两个意向锁存在的意义是:

当事务想去进行锁表时,可以先判断意向锁是否存在,存在时则可快速的返回,告知该表不能启用表锁(也就是会锁住对应会话),提高了加锁的效率。??

自增锁 (AUTO -INC Locks)

针对自增列自增长的一个特殊的表级别锁

可以使用如下语句查看 :

1
2
-- 默认取值1 代表连续 事务未提交则id永久丢失
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';

实际演示效果如下:

img

执行结果如下:

img

行锁的算法

简介

行锁锁的是索引上的索引项

只有通过索引条件进行数据检索,Innodb才使用行级锁。否则,将使用表锁(锁住索引的所有记录)

行锁的算法:

  • 临键锁 Next-Key locks

    当sql执行按照索引进行数据的检索时,查询条件为范围查找(between and < > 等等)并有数据命中,则测试SQL语句加上的锁为Next-Key locks,锁住索引的记录区间加下一个记录区间,这个区间是左开右闭

  • 间隙锁 Gap

    查询条件为范围查找(between and < > 等等)且当记录不存在时,临键锁退化成Gap. 在上述检索条件下,如果没有命中记录,则退化成Gap锁,锁住数据不存在的区间(左开右开

  • 记录锁 Record Lock

    唯一性索引 条件为精准匹配,退化成Record锁. 当SQL执行按照唯一性(Primary Key,Unique Key)索引进行数据的检索时,查询条件等值匹配且查询的数据存在,这时SQL语句上加的锁即为记录锁Record locks,锁住具体的索引项。

举例

临键锁

Next-Key locks 也是 InnoDB 引擎默认的行锁算法.

如图:我们假设一张表中的数据行的id 是 1 4 7 10

img

则innodb会把这个表的数据划分成如图五个区间(因为有四个记录),然后我们执行图中的SQL语句之后,会发现有两个区间被锁住了,一个是(4,7] , 一个是 (7,10]

为了验证这个结论,我做了如下实验:

验证区间是否左开右闭:

img

验证当前记录行是否被锁定:

img

验证是否锁定下一区间:

img

以下两种锁只给出结论,演示过程省略,感兴趣可自行验证哈!都是同样的方法,就不赘述了

间隙锁

img

记录锁

img

总结

MySQL 的 Innodb引擎正是通过上述不同类型的锁,完成了事务隔离:

  • 加 X 锁 避免了数据的脏读
  • 加 S 锁 避免了数据的不可重复读
  • 加上 Next Key 避免了数据的幻读

补充

Innodb中行级锁作用于索引之上,如果没有索引,则只能够锁表。

一次封锁法

为了预防死锁,一般应用中推荐一次封锁法。也就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行完成之后,再进行解锁。

一次封锁法能够预防死锁,但从该方法的定义中可以看到,每次操作都锁住全部数据,如果这样数据的执行只能是串行化的,性能不高。

两阶段锁协议

数据库遵循的是两段锁协议,将事务分解成加锁和解锁两个阶段

加锁阶段

该阶段可以进行加锁操作,在对任何数据进行读操作之前要申请并获得S锁(Shared Lock,其它事务可以继续加S锁,但不能加Exclusive Lock,即排他锁);而在进行写操作之前,需要申请X锁(Exclusive Lock,其它事务不能再获得任何锁)。加锁不成功则进入等待状态,而不能再加其它锁。

从这个定义可以看出,加锁阶段定义了事务之间的协调规则,能够有效提高多个事务之间的执行性能,但同时也带来了死锁的风险,之后会举例介绍死锁的成因。

解锁阶段

事务进入解锁阶段将释放其持有的锁,该阶段只能进行解锁操作,而不能再加其它锁。

Innodb中的各种锁

Shared Lock And Exclusive Locks

这是两个行级锁,包括 Shared Lock(S 共享锁)Exclusive Lock(X 排他锁):

  1. 共享锁 允许持有锁的事务去读取一行数据,可以有多个事务同时持有共享锁,但当数据被加上共享锁时,不能再被加排他锁。
  2. 排他锁 允许持有锁的事务去更新或则删除一行数据,同时只能有一个事务持有排他锁,当数据被加上排他锁时,不能再加共享锁。

Record Locks

记录锁是作用在索引上,比如这么一条语句:

1
SELECT c1 FROM t WHERE c1=10 FOR UPDATE

这条语句将会在c1值为10这条记录的索引加锁,阻止其它事务的插入,更新和删除操作。 即使c1不存在索引,Innodb也会创建一个隐藏的clustered index,并用其作为锁的依据。

Next-key Locks

Next-key锁是记录锁和Gap锁的结合,锁住了记录和记录之前的一段Gap区间。 比如索引包含了10,11,13和20,那么Next-key分出的区间如下:

1
2
3
4
5
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

Intention Locks

Intention Locks(意向锁)是MySQL为了支持不同粒度的锁而设计的一种 表级别锁(但不是通常认为的表锁) ,它表示了表之后将被加上哪种行级锁。意向锁的分类如下:

  1. Intention Shared Lock,意向共享锁(IS) ,表示事务将要在表上加共享锁,规则是在表中申请某些行的共享锁之前,必须先申请IS锁。
  2. Intention Exclusive Lock,意向排他锁(IX) ,表示事务将要在标上加排他锁,规则是在表中申请某些行的排他锁之前,必须先申请IX锁。
1
SELECT ... LOCK IN SHARE MODE

该语句将会在表上加IS锁,同时在对应的记录上加上S锁。

1
SELECT ... FOR UPDATE

该语句将会在标上加上IX锁,同时在对应的记录上加上X锁。

表级锁的兼容性矩阵:

Matrix

总结:意向锁(IX,IS)之间不会产生冲突, 其他情况可将IX锁当做X锁, IS锁当做S锁

事实上意向锁不会和行级的SX锁产生冲突,只会和表级的SX锁产生冲突.

GAP Locks

Gap锁是一种范围锁,Gap锁作用范围是Record锁之间,或者Record锁之前与Record锁之后的范围。

Gap

如图所示,首先当前该记录存在索引,id为5和30的记录将整个分为了 <=5>5&<=30>30 三个区间,如果要更新30的数据,那么 >5 的所有区间都会被锁住。

Insert Intention Locks

Insert Intention Locks也就是插入意向锁,但它其实是一种GAP锁,在行数据被插入之前,设定的一种锁,如果两个事务要插入同一个GAP中的不同行记录,它们都会获取这个GAP的插入意向锁,但相互之间不会冲突。

AUTO-INC Locks

AUTO-INC锁是一种特殊的表级别锁,主要处理表中带有自增列的情况。实际上是为了保证自增的正确性,所以有了这种锁。


本文整理自

【MySQL (四) | 五分钟搞清楚InnoDB锁机制】

Innodb 锁类型详解

仅做个人学习总结所用,遵循CC 4.0 BY-SA版权协议,如有侵权请联系删除!