面试君与面试官的分歧:innodb支持行锁就不锁表么?

面试官观点:innodb引擎只会使用行锁。

面试君观点:innodb引擎支持行锁,但在不使用索引或无索引的时候会使用表锁。

虽然当时未做过多争论,不过这个问题还是挺有意思,MySQL的innodb引擎到底会锁表么?


这个在网上搜索,大部分的回答是innodb是在索引上实现行锁的,如果没有索引或无法利用索引,那么可能会升级为锁表。
从《MySQL技术内幕  InnoDB存储引擎 第2版》第六章“锁”中的描述innodb引擎有两种锁:共享锁排它锁。这两种所都是行锁。
共享锁:允许事物读取一行数据。
排它锁:允许事物删除或更新一行锁。
另外,其还有一种特殊的锁名为意向锁,意向锁也分为两种:意向共享锁意向排它锁。意向锁为表锁
意向共享锁:允许事物读取几行数据。
意向排它锁:允许事物删除或更新机行锁。
为了搞清楚这些概念,需要做一点实验,观察innodb是如何执行锁的。
创建库qw,创建2张表book和noindex。
表book内2个字段,其中id字段为自增长主键,num字段列值非唯一,name字段可为null。
表noindex没有创建主键和索引,其pwd2字段值只有65和31两种(即本列值非唯一)。
 

实验1:在表b上模拟2个排它锁,各锁定1行数据执行更新
打开两个控制台(控制台A,控制台B)分别执行如下:

USE qw;

BEGIN;

UPDATE book SET `name`='a567' WHERE id=15;

#COMMIT;
 

USE qw;

BEGIN;

UPDATE book SET `NAME`='b567' WHERE id=16;

#COMMIT;
注意:两个命令都没有提交,以模拟并发排它锁。

在其他控制台执行:

USE information_schema;

SELECT * FROM INNODB_TRX
观察事务执行状态:

这里显示两个事务都储运运行状态,也就是两者没有冲突。
执行:SELECT * FROM INNODB_LOCKS
此时没有记录。
提交或回滚数据,然后进行第二个实验。

实验2:修改实验1的sql的条件,改为大于和小于
 

USE qw;

BEGIN;

UPDATE book SET `name`='a567' WHERE id<15;

#COMMIT;
 

USE qw;

BEGIN;

UPDATE book SET `NAME`='b567' WHERE id>25;

#COMMIT;
注意:两个条件范围没有交集

这里显示两个事务都储运运行状态,也就是两者没有冲突。
执行:SELECT * FROM INNODB_LOCKS
此时没有记录。

实验3:修改实验1的sql的条件,改为大于和小于,范围有交集

USE qw;

BEGIN;

UPDATE book SET `name`='x67' WHERE id<15;

#COMMIT;
 

USE qw;

BEGIN;

UPDATE book SET `NAME`='y56' WHERE id<20 and id >13;

#COMMIT;
注意:两个条件范围有交集


这里发现其中一个事务处于锁等待状态。
容易理解,拍它锁不能同时用于同一行。
执行:SELECT * FROM INNODB_LOCKS

此时可以看到2个排它锁

实验4:使用无索引表noindex执行对1条数据更新操作

USE qw;

BEGIN;

UPDATE noindex SET pwd2='a567' WHERE name1='n2';

COMMIT;
 

USE qw;

BEGIN;

UPDATE noindex SET pwd2='a567' WHERE name1='n12';

COMMIT;

注意:表noindex没有索引,但是name1没有重复值(可以精品匹配)

如图,其中一个事务处于等待。


上面做了4个实验,总结如下:

  锁行数 主键或索引 是否有交集 事务状态
实验1 1 有,使用 两个事务同时运行
实验2 多行 有,使用 两个事务同时运行
实验3 多行 有,使用 一个事务运行,一个等待
实验4 1 一个事务运行,一个等待

分析:
实验1,有索引并使用作为条件,锁1行数据无交集,2个事务排它锁没有锁冲突,因此可以同时运行。
实验2,有索引并使用作为条件,但条件为一个范围(欲锁多条数据),事务A和事务B条件无交集,排它锁没有锁冲突,因此可以同时运行。
实验3,有索引并使用作为条件,但条件为一个范围(欲锁多条数据),事务A和事务B条件有交集,排它锁有锁冲突(后执行的事务发现欲加锁的行已经有排它锁),因此不可以同时运行。
实验4,没有索引,条件为精确匹配(数据唯一),事务A和事务B条件无交集,排它锁按道理不应该有冲突,事务可以同时运行,但是,实际情况是从innodb_trx看到两个事务有一个在等待。在innodb_lock内看到事务的锁类型是RECORD,是行锁。不能同时运行的原因只能是事务B(后运行的事务)要锁定的行,被事务A锁定了。然而根据条件事务A只需要锁1行,这是否就印证了innodb的锁是依靠索引来锁的?因为没有索引,所以就锁了全部的行。
是不是真的锁了全部记录?测试一下,对所有行尝试update,结果均出现阻塞。insert数据也是阻塞。
结论是确实锁了全部的行。虽然是行锁,但实际上是全表锁定。

为了进一步证实跟索引有关,我们在表nobook的name1字段添加索引,在进行测试。

两个事务已经可以同时运行。这印证了行锁需要索引支持。

面试官和面试君的分歧还无法给出明确的结论。从实验数据来看lock_type没有出现table,但是在无索引情况下虽然是行锁,但是锁了所有的行。最后逇实验也证实了行锁需要索引支持来实现。因此二人所说都有一定道理。

补充实验5:使用表book的索引字段进行更新操作。字段aid有创建索引但是不唯一。

BEGIN ;
UPDATE qw.book SET num=24 WHERE `name`='x567';
#commit;

USE qw;
BEGIN ;
UPDATE qw.book SET num=23 WHERE `name`='y567';

#commit;

其现象与实验4一致。

使用explain对更新语句分析

表book记录总数1930条,mysql优化器给出的预测是会影响1930条记录,也就是全表。表有主键但是没有可利用的索引。

如果条件可以上使用主键,那么影响的数据条数会怎样?

面试君与面试官的分歧:innodb支持行锁就不锁表么?_第1张图片

这个语句虽然是一个范围条件,但是优化器认为可以使用主键,并且影响的数据为20条。

补充实验6:这次数据条件有变化,看下图

面试君与面试官的分歧:innodb支持行锁就不锁表么?_第2张图片

字段aid有重复值,且不连续。

分别在两个控制台执行如下:

BEGIN ;
SELECT * FROM noindex WHERE aid=15 FOR UPDATE;
#commit ;

BEGIN;
INSERT INTO noindex(name1,pwd2,aid) VALUES('n18','22',14);
#commit;

此时观察innodb_locks表如下:

后执行的sql除了X之外,多了一个GAP(间隙锁)。这个是锁定一个范围的算法,不包括记录本身。

如果将第二句aid值改为16:

BEGIN;
INSERT INTO noindex(name1,pwd2,aid) VALUES('n18','22',
16);
#commit;

面试君与面试官的分歧:innodb支持行锁就不锁表么?_第3张图片

先执行的sql也出现了GAP。

根据资料介绍,innodb引擎使用在不唯一索引条件是,实际会自动添加GAP锁。本例中条件a=15 for update,引擎添加了GAP锁,锁定了(6,20)范围,15前一个值是6,后一个值是20.在这一范围内插入数据时,因为GAP的存在而会处于等待。

而之外的值不会被阻塞。比如改为aid值22

INSERT INTO noindex(name1,pwd2,aid) VALUES('n18','22',22);

这句执行则不会受第一个sql锁定影响。

第二条要插入aid为14的记录,因为第一条锁定aid=15之外还想前锁定最近一条记录形成一个范围(最近的是6),14刚好在范围内,所以必须等待第一个操作释放锁后才能执行。

小结:经过学习资料和实验,基本可以得出以下结论

1.innodb支持行锁,并且不升级表锁

   主要原因是行锁开销比较小,3千万条记录的表,锁全部行大约使用90M内存。

2.innodb行锁在非唯一辅助索引上,锁定不是一行记录,是一个范围

   相反,如果是唯一的辅助索引,是锁一行记录。

3.锁冲突时,后发生的锁请求会等待(阻塞)。  如果事物之间形成锁请求等待依赖闭环,就会出现死锁。

  wait-for-graph是一种死锁预测方式,有助于预防死锁。

你可能感兴趣的:(MySQL)