Innodb加锁机制(隐式锁)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sun_ashe/article/details/82683296

隐式锁

简介

Innodb采用乐观插入的方式,所以在做insert操作时不会进行显示加锁,也就是不会生成lock_t锁结构,这样可以极大的减少锁开销,提升整体的性能。

如果没有显示的行锁,该如何保证事务插入的正确性呢?比如说如下两个事务,插入相同的两个主键数据,如下:

create table test(id int not null auto_increment primary key,name varchar(10));
session1 session2
begin; begin;
insert into test values(1,’test’);
insert into test values(1,’test’);

在事务1提交之前,事务2是不会报错主键冲突的,会陷入等待的状态,那么这种阻塞是如何形成的,将会通过这篇文章进行详细介绍。

详解隐式锁

Innodb的行锁结构是基于行数据的,而隐式锁没有真正的行锁结构,它通过每一行数据的隐藏字段来完成。Innodb主键索引的每一行数据包含两个隐藏列,其中一个是trx_id,另外一个是回滚段指针。而主键索引上的隐式锁就是通过trx_id来进行设置的,二级索引上的行数据没有trx_id,但是每一个数据页上包含
一个max_trx_id;

隐式锁加锁流程

在开始研究Insert加锁流程时,一直没看出来是如何操作的,因为在进入加锁代码逻辑中,没有看到设置行数据trx_id的过程。这个过程其实实在之前完成的,在构建Innondb 行数据的时候,就已经把trx_id设置进去了。

  • InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于簇索引的B+Tree中。
  • 假设只有主键索引,则在进行插入时,行数据的trx_id被设置为当前事务id
  • 假设存在二级索引,则在对二级索引进行插入时,需要更新所在page的max_trx_id.

判断是否存在隐式锁

对于主键而言,隐式锁的判断只需要查看当前行数据的隐藏列 trx_id是否是活跃事务即可。

对于二级索引会比较麻烦

  • 假设R1是二级索引数据,想要检测R1上是否存在其他的事务拥有隐式锁
  • 获取R1所在的数据页的最大事务ID T1
  • 假设T2是当前事务系统中的最小活跃事务id,如果T1

隐式锁转换

只有在特殊情况下,才会发生隐式锁到显示锁的转换。这个转换动作并不是加隐式锁的线程自发去做的,而是其他存在行数据冲突的线程去做的。

还是看上面的场景:

表结构

create table t1(id int not null auto_increment primary key,name varchar(10))
操作序号 session-1 session-2
1 begin; begin;
2 insert into t1(id,name) values(1,’aaa’);
3 insert into t2(id,name) values(1,’aaa’);

操作2时,s1对行数据(1,’aaa’)加隐式锁,不需要lock_t结构,不需要添加到lock hash table中。show engine innodb status结果如下

------------
TRANSACTIONS
------------
Trx id counter 329052
Purge done for trx's n:o < 329051 undo n:o < 0 state: running but idle
History list length 117
Total number of lock structs in row lock hash table 0 /*lock hash table中没有记录*/
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 281479605203232, not started
0 lock struct(s), heap size 1160, 0 row lock(s)
---TRANSACTION 281479605201056, not started
0 lock struct(s), heap size 1160, 0 row lock(s)
---TRANSACTION 329051, ACTIVE 6 sec
1 lock struct(s), heap size 1160, 0 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 123145543389184, query id 87 localhost root

操作3时
锁日志如下:
第一,trx_id=329051的事务,也就是第一条插入语句,增加了一个行锁,并且插入到lock hash table中。

2018-09-12T14:45:33.715550+08:00 5 [Note] InnoDB: trx_id: 329051 create a record lock and add it to lock hash table,
space_id: 195
page_no: 3
heap_no: 2
n_bits: 72
primary key: 1
is record lock: 1
is waiting: 0
is gap: 0
is record not gap: 1
is insert intention: 0
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

第二,trx=329052的事务,也加了一把锁(heap_no: 2,第一条用户数据;间隙锁,共享)

2018-09-12T14:45:33.723318+08:00 5 [Note] InnoDB: trx_id: 329052 create a record lock and add it to lock hash table,
space_id: 195
page_no: 3
heap_no: 2
n_bits: 72
primary key: 1
is record lock: 1
is waiting: 1
is gap: 0
is record not gap: 0
is insert intention: 0
lock_mode: 2  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

第一个锁结构就是由隐式锁转换而来的,而第二把锁处于waiting状态,因为XS冲突导致的。造成如上这种等待的逻辑:

  • 事务1 在进行插入操作时,通过游标定位到待插入行数据无数据冲突
  • 事务2 在进行插入操作时,通过游标定位到待插入行数据存在主键冲突
  • 获取冲突行数据的trx_id
  • 检测行数据的trx_id是否为活跃事务id,如果不活跃,则直接返回报错主键冲突
  • 如果活跃,则创建显示锁结构,并且加入到lock hash table中
  • 随后创建事务2尝试对行记录加锁,但是会遇到锁冲突
  • 随后添加对应行锁,并且返回DB_LOCK_WAIT
  • 上层检索到DB_LOCK_WAIT则进入等待状态,等待结束或者等待超时后,再进行其他相关处理。

隐式锁转换在5.7中的优化

关于这部分的优化,可以转到https://yq.aliyun.com/articles/41123

相关博客地址

mysql-5.7对隐式锁转换的优化
https://yq.aliyun.com/articles/41123

Innodb insert操作的大概流程
https://blog.csdn.net/weixin_40581617/article/details/80623276

隐式锁转换的加锁原理图
http://blog.itpub.net/31493717/viewspace-2150833/

猜你喜欢

转载自blog.csdn.net/sun_ashe/article/details/82683296