首先我们有三张表t1,t2,t3,它们都是只有两个字段, int类型的id和varchar类型的name;区别是t1没有索引,t2有主键索引,t3有唯一索引。
再强调一次,在实验前必须提前关闭自动提交,
set autocommit=off
。然后show variables like 'autocommit'
查看自动提交是否是off。
我们先假设InnoDB的锁锁住了是一行数据或者一条记录。
1.假设锁住记录
1.1 实验一:没有索引的表(t1)
这个实验操作是操作没有索引的t1,t1里面有4条数据:1、2、3、4。
现在我们在两个会话里面手工开启两个事务。在第一个事务里面,我们通过where id =1 锁住第一行数据。在第二个事务里面,我们尝试给id=3的这一行数据加锁,大家觉得能成功吗?
Transaction1 | Transaction2 |
---|---|
Begin; | |
SELECT * FROM t1 where id=1 FOR UPDATE; | |
Begin; | |
SELECT * FROM t1 where id=3 FOR UPDATE; // BLOCKED | |
INSERT INTO t1 (id, name) VALUES (5, ‘5’); // BLOCKED |
这就有点奇怪了,第一个事务锁住了id=1的这行数据,为什么我不能操作id=3的数据呢?我们再来操作一条不存在的数据,插入id=5。它也被阻塞了。实际上这里整张表都被锁住了。所以,我们的第一个猜想被推翻了,InnoDB的锁锁住的应该不是Record。
那为什么在没有索引或者没有用到索引的情况下,会锁住整张表?这个问题我们先留在这里。下面继续看第二个实验。
1.2 实验二:有主键索引的表(t2)
我们先看一下t2的表结构。字段是一样的,不同的地方是id上创建了一个主键索引。里面的数据是 1、4、7、10。
Transaction1 | Transaction2 |
---|---|
Begin; | |
SELECT * FROM t2 where id=1 FOR UPDATE; | |
Begin; | |
SELECT * FROM t2 where id=1 FOR UPDATE; // BLOCKED | |
SELECT * FROM t2 where id=4 FOR UPDATE; // OK |
第一种情况,使用相同的id值去加锁,冲突;使用不同的id加锁,可以加锁成功。
那么出现问题了,从实验一中得到锁定的不是一行数据,但是实验二操作不同记录的数据又可以成功,那有没有可能是锁住了id的这个字段呢?
2.假设锁住字段
我们看一下 t3 的表结构。字段还是一样的, id上创建了一个主键索引,name上创建了一个唯一索引。里面的数据是1、4、7、10。
在第一个事务里面,我们通过name字段去锁定值是4的这行数据。在第二个事务里面,尝试获取一样的排它锁,肯定是失败的,这个不用怀疑。在这里我们怀疑InnoDB锁住的是字段,所以这次我换一个字段,用id=4去给这行数据加锁,大家觉得能成功吗?
Transaction1 | Transaction2 |
---|---|
Begin; | |
SELECT * FROM t3 where name=‘4’ FOR UPDATE; | |
Begin; | |
SELECT * FROM t3 where name=‘4’ FOR UPDATE; // BLOCKED | |
SELECT * FROM t3 where id=4 FOR UPDATE; // BLOCKED |
很遗憾,又被阻塞了,说明锁住的是字段的这个推测也是错的,否则就不会出现第一个事务锁住了name,第二个字段锁住id失败的情况。
既然锁住的不是record,也不是column, InnoDB里面锁住的到底是什么呢?
3.其实,锁的是索引
在这三个案例里面,我们要去分析一下他们的差异在哪里,也就是这三张表的结构,是什么区别导致了加锁的行为的差异?其实答案就是索引。 InnoDB的行锁,就是通过锁住索引来实现的。
那索引又是个什么东西?为什么它可以被锁住?我们在 【MySQL】详谈索引存储结构推演过程 已经分析过了。那么我们还有两个问题没有解决:
问题一:为什么表里面没有索引的时候,实验一锁住一行数据会导致锁表?或者说,如果锁住的是索引,一张表没有索引怎么办?所以,一张表有没有可能没有索引?
- 如果我们定义了主键(PRIMARYKEY),那么 InnoDB 会选择主键作为聚集索引
- 如果没有显式定义主键,则 InnoDB 会选择第一个不包含有 NULL 值的唯一索引作为主键索引
- 如果也没有这样的唯一索引,则 InnoDB 会选择内置 6 字节长的 ROWID 作为隐藏的聚集索引,它会随着行记录的写入而主键递增
所以,实验一为什么锁表,是因为查询没有使用索引,会进行全表扫描,然后把每一个隐藏的聚集索引都锁住了。
问题二:实验二为什么通过唯一索引给数据行加锁,主键索引也会被锁住?
在辅助索引里面, 索引存储的是二级索引和主键的值。 比如name=4,存储的是name的索引和主键id的值4。而主键索引里面除了索引之外,还存储了完整的数据。所以我们通过辅助索引锁定一行数据的时候,它跟我们检索数据的步骤是一样的,会通过主键值找到主键索引,然后也锁定。
复杂官网的一句话,A record lock is a lock on an index record,是不是只有【记录锁】是锁索引,其他锁是锁住行或表?
回复:行锁、表锁是对于锁粒度而言的,是一对最广泛的概念。表锁的实现很好想,就是需要一个标志来记录当前有没有事务已经来操做表了。而行锁的实现是锁的索引,根据锁索引的范围又可以分为记录锁、间隙锁、临键锁。 比如说我们修改一个数据库已经有的记录,那直接锁相应索引就行(记录锁);再比如我们给一张有两条数据的表(id=1,id=10)进行范围查询并加锁 where id>2 and id < 5 for update,此时命中了一个不存在数据的区间(2,5),这时该锁哪?是锁两个索引之间的整个区间(1,10),这就是间隙锁。临键锁=记录锁+间隙锁,这里就不说了,可以看我的这篇文章 https://blog.csdn.net/weixin_43935927/article/details/109410420