next-key locking

都忘记自己写的都是些什么东西了。。。X锁是写锁/排他锁,S锁是读锁/共享锁。

----------------------------------------------

在centos7 mysql 5.7.27里, 默认的事务隔离级别是repeatable read, 默认使用next-key locking. 

我看到很多资料和文章都只解释了等值查询for update加X锁的情况, 那如果是大于或者大于等于查询呢?

以下是对next-key locking的一些理解, 我就不写太多概念的东西了, 难理解. 

create table n(a int, b varchar(10), c int, primary key(a), key(c));
insert into n select 1, '1', 1;
insert into n select 10, '10', 10;
insert into n select 20, '20', 20;
insert into n select 30, '30', 30;
insert into n select 50, '50', 50;

autocommit是连接级别的一个变量, 默认是打开的:

先把它关掉:

set @@autocommit=0;

 

 

如下语句会锁住[30, +无穷大):

select c from n where c>30 for update;

 

如下语句会锁住[20, 30]和[30, +无穷大), 其实就是[20, +无穷大):

select c from n where c>=30 for update;

其实是锁定前一个索引值到30之间, 以及30到+无穷大之间, 也包括30本身.

这些锁定都是在c索引上, c索引是建立在c列上的一个允许重复的普通索引.

 

Update不存在的行数据

会使用next key locking.

表结构和数据如下:

CREATE TABLE sorderdetail(  
    id int NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'Primary Key',
    orderid int NOT NULL,
    productid int NOT NULL,
    description VARCHAR(255),
    KEY(orderid), KEY(productid)
) COMMENT '';
insert into sorderdetail(id, orderid, productid, description) values(1, 1,1,"test description");
insert into sorderdetail(id, orderid, productid, description) values(2, 10,10,"test description");
insert into sorderdetail(id, orderid, productid, description) values(3, 15,15,"test description");
insert into sorderdetail(id, orderid, productid, description) values(13, 19,19,"19 test description");
insert into sorderdetail(id, orderid, productid, description) values(4, 20,20,"test description");
insert into sorderdetail(id, orderid, productid, description) values(5, 50,50,"50 xxx test description");
insert into sorderdetail(id, orderid, productid, description) values(8, 51,51,"51 test description");
insert into sorderdetail(id, orderid, productid, description) values(22, 75,75,"75 test description");
insert into sorderdetail(id, orderid, productid, description) values(6, 80,80,"80 test description");
insert into sorderdetail(id, orderid, productid, description) values(33, 90,90,"90 test description");

  

 orderid和productid都是普通索引, 先开启事务A, 对于以下语句, 在repeatable read级别下会用S锁住进行next key locking:

begin;
update
sorderdetail set description='333' where productid=100;

在其他窗口查看整个数据库锁的情况:

select OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS, `LOCK_DATA` from performance_schema.data_locks;

 

 其中第一行是一个表级别的X意向锁,防止有其他事务想锁住整个表。第二行是在productid索引上加的区间锁(90, +无穷大),注意此时是不包含索引的最大值90的,个人认为也算合理,因为语句摆明了是要修改productid为100的数据,锁住90算嘛事呀?

事实证明,两个事务之间加表级别的意向排他锁时不会互斥,对于索引超限排他行锁(也就是`X`+`supremum pseudo-record`)也不互斥:

 

 

 但是`X,INSERT_INTENTION`+`supremum pseudo-record`就会和`X`+`supremum pseudo-record`锁互斥了,insert语句会加`X,INSERT_INTENTION`锁甚至会加`X,GAP,INSERT_INTENTION`锁。

此时如果事务B执行类似的语句:

begin;
update
sorderdetail set description='333' where productid=101;

如此是可以执行成功的, 因为也只会在(90, +无穷大)加S锁.

接下来, 事务A执行如下语句:

insert into sorderdetail(orderid, productid, description) values(90,90," 90 test description");

将会卡住, 因为insert语句需要加X锁, 对于以上语句, 是需要对90这个productid索引加X锁, 但是由于上面事务B持有[90, +无穷大)的S锁以及X锁的排它性, 事务A此时在等事务B释放[90, +无穷大)的S锁, 所以无法执行.

事务A在等待的过程中, 事务B如果再执行如下语句:

insert into sorderdetail(orderid, productid, description) values(92,92," 92 test description");

类似的, 这条insert需要对92加X锁, 但由于事务A也持有了[90, +无穷大)的S锁以及S锁与X锁不兼容, 所以, 事务B在等事务A释放[90, +无穷大)上的S锁, 而事务A也在等事务B释放[90, +无穷大)的S锁, 形成死锁.

此时innodb引擎会检测到这里形成了死锁, 会自动把事务B回滚, 让事务A执行下去.

如果不是尝试插入100而是尝试插入70, 那么会用next key locking的区间就是[51, 75), 原理类似.

 

二级索锁和主键锁

现在有一个奇怪的问题:

事务A尝试update一条不存在的记录76, 按理说此时会用next key locking在[75, 80)上加S锁, 这么一来, 事务B应该是既无法insert 75, 也无法update 75的记录才对, 可是事实是, 事务B还是可以update 75, 但是insert会被要求等待锁. 为什么事务B可以update 75呢??? 稍后回来思考~~

注意这里的事务A加的S锁区间[75, 80)只存在于productid这个索引上, 而在主键索引上没有加任何锁. 事务B如果要update 75的话, 所加S锁在75上,所加X锁在id=22的主键索引上, id=22的主键索引上没有任何锁, 所以事务B可以执行这句update语句. 而insert? 是又需要在product索引上75加X锁的, 所以和事务A的S锁区间[75, 80)冲突, 无法执行.

以上.

 

posted @ 2019-08-18 23:47  透明飞起来了  阅读(573)  评论(0编辑  收藏  举报