理解innodb的锁(record,gap,Next-Key lock)

Record lock

单条索引记录上加锁,record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁,这个类似于表锁,但原理上和表锁应该是完全不同的。


Gap lock

在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。gap lock的机制主要是解决可重复读模式下的幻读问题,关于幻读的演示和gap锁如何解决了幻读。关于这一块,先给出几个定义


快照读:

简单的select操作,没有lock in share mode或for update,快照读不会加任何的锁,而且由于mysql的一致性非锁定读的机制存在,任何快照读也不会被阻塞。但是如果事务的隔离级别是SERIALIZABLE的话,那么快照读也会被加上共享的next-key锁,本文不对SERIALIZABLE隔离级别做叙述。

当前读:

官方文档的术语叫locking read,也就是insert,update,delete,select..in share mode和select..for update,当前读会在所有扫描到的索引记录上加锁,不管它后面的where条件到底有没有命中对应的行记录。当前读可能会引起死锁。

意向锁:

innodb的意向锁主要用户多粒度的锁并存的情况。比如事务A要在一个表上加S锁,如果表中的一行已被事务B加了X锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。为了解决这个问题,可以在表级上引入新的锁类型来表示其所属行的加锁情况,这就引出了“意向锁”的概念。举个例子,如果表中记录1亿,事务A把其中有几条记录上了行锁了,这时事务B需要给这个表加表级锁,如果没有意向锁的话,那就要去表中查找这一亿条记录是否上锁了。如果存在意向锁,那么假如事务A在更新一条记录之前,先加意向锁,再加X锁,事务B先检查该表上是否存在意向锁,存在的意向锁是否与自己准备加的锁冲突,如果有冲突,则等待直到事务A释放,而无须逐条记录去检测。事务B更新表时,其实无须知道到底哪一行被锁了,它只要知道反正有一行被锁了就行了。
说白了意向锁的主要作用是处理行锁和表锁之间的矛盾,能够显示“某个事务正在某一行上持有了锁,或者准备去持有锁”

不可重复读:

指的是在同一个事务中,连续几次快照读,读取的记录应该是一样的

不可重复读的演示较为简单,本文不做讨论。

幻读:

指的是在一个事务A中执行了一个当前读操作,而另外一个事务B在事务A的影响区间内insert了一条记录,这时事务A再执行一个当前读操作时,出现了幻行。这和不可重复读的主要区别就在与事务A中一个是快照读,一个当前读;并且事务B中一个是任何的dml操作,一个只是insert。比如在A中select * from test where id<10 lock in share mode结果集为(1,2,3),这时在B中对test表插入了一条记录4,这时在A中重新查询结果集就是(1,2,3,4),和事务A在第一次查询出来的结果集不一致,这里的4就是幻行。

演示条件:由于可重读的隔离级别下,默认采用Next-Key Locks,就是Record lock和gap lock的结合,即除了锁住记录本身,还要再锁住索引之间的间隙,所以这个gap lock机制默认打开,并不会产生幻行,那么我们要演示幻行的话,要么将隔离级别改为read-commited,要么在REPEATABLE-READ模式下禁用掉gap lock,这里我们采用的是第二种方式。


幻读的演示

在演示之前又引入了innodb_locks_unsafe_for_binlog参数,该参数可以禁用gap lock。

innodb_locks_unsafe_for_binlog:静态参数,默认为0,表示启动gap lock,如果设置为1,表示禁用gap lock,这时mysql就只有record lock了,不过值得注意的是,即使了设置了1,关于外键和唯一键重复检查方面用到的gap lock依旧有效。这时可以简单地理解成事务的隔离级别退化成可重复读,然后两者应该还是有所区别的。建议是不要随便设置,我们这里设置只是做个简单的幻读演示,mysql后续的版本可能都会废弃掉这个参数了。


session 1 先将myid>95的记录加一个当前读
mysql> show create table test_gap_lock\G
*************************** 1. row ***************************
       Table: test_gap_lock
Create Table: CREATE TABLE `test_gap_lock` (
  `id` int(11) NOT NULL,
  `name` varchar(100) DEFAULT NULL,
  `myid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_name` (`name`),
  KEY `idex_myid` (`myid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> begin;
mysql> select * from test_gap_lock where myid>95 for update;
+----+------------+------+
| id | name       | myid |
+----+------------+------+
|  1 | jiang      |   99 |
|  2 | hubingmei  |   99 |
|  5 | hubingmei4 |  100 |
+----+------------+------+
3 rows in set (0.00 sec)


session 2  这时session 2插入myid=98的记录成功了。
insert into test_gap_lock values(6,'jiang2',98);
Query OK, 1 row affected (0.00 sec)


session 1 这时session 1再次查看时发现记录myid=98的记录已经存在了,这条记录就是幻行。
mysql> select * from test_gap_lock where myid>95 for update;
+----+------------+------+
| id | name       | myid |
+----+------------+------+
|  
  • 16
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值