目录
准备
查看锁命令
演示
普通的select语句
共享锁与排他锁
无索引行锁升级为表锁
间隙锁&临键锁
索引上的等值查询(索引为唯一索引)
索引上的等值查询(索引为普通索引)
索引上的范围查询(唯一索引)
准备
我的mysql版本是8。
CREATE TABLE `user` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` tinyint unsigned DEFAULT NULL COMMENT '年龄',
`gender` tinyint unsigned DEFAULT NULL COMMENT '性别, 1:男, 2:女',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
PRIMARY KEY (`id`),
KEY `id_age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
演示锁的时候,就通过上面这张表来演示一下。
查看锁命令
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
常见的 lock_mode
值有:
-
IS
(Intent Shared Lock):意图共享锁。表示事务打算获取共享锁,但当前只在上级资源(如表)上锁定,而不是具体的行或页。 -
IX
(Intent Exclusive Lock):意图排他锁。表示事务打算获取排他锁,但当前只在上级资源(如表)上锁定。 -
S
(Shared Lock):共享锁。多个事务可以同时持有共享锁,允许读取资源,但禁止修改。 -
X
(Exclusive Lock):排他锁。一个事务独占此锁,可以读取和修改资源,其他事务无法访问该资源。 -
SIX
(Shared Intent Exclusive Lock):共享意图排他锁。表示事务允许共享访问某些资源,同时声明可能会对部分资源进行排他操作。 -
SRX
(Shared Read Exclusive Lock):共享读排他锁。类似于共享锁,同时表示将对某些资源进行排他访问。 -
S+
(Next-key Shared Lock):下一键共享锁,InnoDB的特殊锁模式,用于实现间隙锁机制,防止幻读。 -
X+
(Next-key Exclusive Lock):下一键排他锁,InnoDB的特殊锁模式,用于锁定间隙及其后的索引键,适用于更新或删除操作。 -
AUTO-INC
(Auto-Increment Lock):自增锁,专门用于处理AUTO_INCREMENT列,确保插入操作的原子性和顺序。
演示
普通的select语句
普通的select语句,执行时,不会加锁。
共享锁与排他锁
(1)select...lock in share mode,加共享锁,
共享锁与共享锁之间兼容。
共享锁与排他锁之间互斥。
(2)update语句加排他锁
排他锁与排他锁之间互斥。
当客户端一执行update语句,会为id为1的记录加排他锁;
客户端二如果也执行update语句,更新id为1的数据,也要为id为1的数据加排他锁,但是客户端二会处于阻塞状态,因为排他锁之间是互斥的。
直到客户端一把事务提交了,才会把这一行的行锁释放,此时客户端二解除阻塞。
无索引行锁升级为表锁
在两个客户端中执行如下操作:
在客户端一中开启事务,并执行update语句,更新name为白眉鹰王的数据,也就是id为1的记录。
然后在客户端二中更新iid为1和d为3的记录,却不能直接执行,会处于阻塞状态,为什么呢?
原因就是此时客户端一根据name字段进行更新时,name字段是没有索引的,如果没有索引,此时行锁会升级为表锁(因为行锁是对索引项加的锁,而name没有索引)。
接下来,我们再针对建立索引但的字段age,索引建立之后,再次做一个测试:
此时我们可以看到,客户端一开启事务,根据age进行更新。而客户端二在更新id为3的数据时,更新成功,并未进入阻塞状态。
这样就说明,我们根据索引字段进行更新操作,就可以避免行锁升级为表锁的情况。
间隙锁&临键锁
索引上的等值查询(索引为唯一索引)
(1)当对唯一索引上进行等值查询时,给存在的记录加锁时, 是记录锁。
这意味着锁定的对象是索引记录本身,而不是记录之间的间隙。REC_NOT_GAP
锁用于精确锁定某个索引记录,以防止其他事务对该记录的修改或删除,但不影响索引记录之间的空隙。这种锁常用于阻止其他事务更新或删除当前事务所锁定的索引记录,同时允许其他事务插入新的记录到该索引记录之间的间隙。
(2)当对唯一索引上进行等值查询时,给不存在的记录加锁时, 优化为间隙锁。
X,GAP
表示当前事务在一个索引的间隙上加了一个排他锁。这意味着在当前事务持有该锁期间,其他事务不能在该间隙中插入新记录。这种锁定机制通常用于防止并发事务产生幻读问题,从而确保事务隔离级别为 REPEATABLE READ
或 SERIALIZABLE
。
索引上的等值查询(索引为普通索引)
当非唯一普通索引进行等值查询时,向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。
分析一下,我们知道InnoDB的B+树索引,叶子节点是有序的双向链表。
假如我们要根据这个二级索引查询值为18的数据,并加上共享锁,我们是只锁定18这一行就可以了吗?
并不是,因为是非唯一索引,这个结构中可能有多个18的存在,所以,在加锁时会继续往后找,找到一个不满足条件的值(当前案例中也就是29)。此时会对18加临键锁,并对29之前的间隙加锁。
(1)如果等值查询的记录存在,
(2)如果等值查询的记录不存在,
索引上的范围查询(唯一索引)
当对唯一索引上进行范围查询时,会访问到不满足条件的第一个值为止。
(1)查询的条件为id>=10,并添加共享锁。 此时我们可以根据数据库表中现有的数据,将数据分为4个部分:
- [10]
- (10,16]
- (16,20]
- (20,+∞]
所以数据库数据在加锁时,就是将10加了行锁,16的临键锁(包含16及16之前的间隙),20的临键锁(包含20及20之前的间隙),正无穷的临键锁(正无穷及之前的间隙)。
(1)如果查询的条件为id>=11,并添加共享锁。 此时我们可以根据数据库表中现有的数据,由于id=11不存在,因此将数据分为3个部分:
- (10,16]
- (16,20]
- (20,+∞]
所以数据库数据在加锁时,就是加了16的临键锁(包含16及16之前的间隙),20的临键锁(包含20及20之前的间隙),正无穷的临键锁(正无穷及之前的间隙)。