第八篇:MySQL之锁详解

本文深入探讨了MySQL中的事务并发问题,通过实例详细解释了加锁的原因,区分了快照读和当前读,并介绍了不同隔离级别下的读写操作如何加锁。内容涵盖了行锁、表锁、意向锁、间隙锁和NEXT-KEY锁的原理及应用,以及自增锁和插入意向锁的作用。此外,还分析了不同隔离级别(读未提交、读已提交、可重复读、串行化)下的锁行为差异,为理解MySQL事务处理和并发控制提供了全面的视角。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、加锁的原因

事务并发造成的数据不一致,举例如下:
事务A:update test set k = k + 1 where id = 1;
事务B:update test set k = k + 1 where id = 1;
按照正常执行的话,提交完事务A和B后,k会变成k+2;
如果不对对应记录加锁的话,事务B和事务A同时更新的话,会导致k变成k+1,导致数据丢失。

2、快照读和当前读区别

我们都知道MySQL中的读分为两种:
1、快照读: 读取的不一定是最新的数据,读取的是MVCC为事务提供的快照。
除了串行化的隔离级别之外,其他级别的快照读是不会加任何锁的。
串行化的加锁和可重复读隔离级别的当前单独加锁是一样的。

select xx from xx;

2、当前读: 每次读取的就是当前数据记录对应的最新值!
select xx from xx lock in share mode
select xx from xx for update
update xx set xx = xx
delete from xx where xx = xx
insert into xx values()

对于不同的隔离级别,会加不同的锁。

3、MySQL中的加锁方式:

MySQL中事务采用两阶段加锁方式:
两阶段指的是:
阶段1、当事务中的语句开始执行时,才会申请对应的锁
阶段2、当事务提交或者回滚后,事务中所有语句申请的锁才会释放。
两阶段锁
如上图所示,当执行sql语句2的时候才会申请对应的锁,当事务Acommit后才会sql语句2对应的锁。sql语句1也是。

4、MySQL中锁的类型:

MySQL中的锁按照读写分为两种:
1、X锁
X锁叫做排他锁,也叫做写锁,当写数据的时候需要申请X锁。当申请X锁时,数据上不能有X锁和S锁。也就是当其他事务在数据上写数据或者读数据的时候,新的事务不能申请X锁,写数据,当前事务只能被阻塞,等待其他事务释放锁之后,才能申请。
2、S锁
S锁叫做共享锁,也叫做读锁,当读数据的时候需要申请S锁。当申请S锁时,数据上不能有X锁。可以有S锁,也就是当其他事务正在写数据的时候,当前事务不能读取当前数据。S锁读取数据指的是当前读,快照读是不需要加锁的。

MySQL中锁按照加锁对象分为以下四种:
1、行锁
行锁就是加在数据行上的锁,分为两种:
1、S锁:加在数据行上的读锁 sql语句为:select xx from xx lock in share mode;
2、X锁:加在数据行上的写锁 sql语句为:select xx from xx for update

2、表锁
表锁是直接加在表上的锁,分为四种:
1、S锁:加在表上的读锁。sql语句为 lock tables xx read;
2、X锁:加在表上的写锁。sql语句为 lock tables xx write;
3、IS锁:加在表上的意向读锁,当事务申请行锁中的S锁时,MySQL会 自动为表申请IS锁。
4、IX锁:加在表上的意向写锁。当事务申请行锁中的X锁时,MySQL会自动为表申请IX锁。

IS 锁 IX锁 行S锁 行X锁 表S锁 表X锁
IS锁 兼容 兼容 兼容 兼容 兼容 冲突
IX锁 兼容 兼容 兼容 兼容 冲突 冲突
行S锁 兼容 兼容 兼容 冲突 兼容 冲突
行X锁 兼容 兼容 冲突 冲突 冲突 冲突
表S锁 兼容 冲突 兼容 冲突 兼容 冲突
表X锁 冲突 冲突 冲突 冲突 冲突 冲突

为什么会有意向锁呢?
当事务申请表X锁时,需要表中的每条数据行都不能有行S锁和行X锁。如果没有意向锁的话,MySQL需要从头到尾依次扫描每行来判断是否存在行锁,这种做法效率低下。所以,引入意向锁,当事务申请行S锁时,会自动申请IS锁,当事务申请行X锁时,会自动申请IX锁。

IS锁和IX锁是兼容的
IS锁和IX锁是用来克制表锁的,IS锁和IX锁分别对应的为行S锁和行X锁,因为一个表中有很多数据行,同一个数据行的S锁和X锁才会冲突。所以IS锁和IX锁是兼容的。在行锁申请完意向锁后,还需要进一步申请对应的行锁,如果对应的行锁获取不到,事务仍然要被阻塞。

3、间隙锁

我们都知道,MySQL中的数据是以B+树的形式存在的。一个索引对应一个B+树。非叶子节点存放的是索引数据,叶子节点中存放的是对应的数据行。并且叶子节点是按照某一列的顺序排列的。
比如某一列的数据为[0,5,10,15];
则该列对应区间有(-无穷,0),(0,5),(5,10),(10,15),(15,+无穷)。区间对应的就是间隙。间隙锁就是在间隙上加锁,与插入本区间的插入操作冲突,防止插入,用来解决幻读问题,这个我们到后面再细说。

4、NEXT-KEY 锁
NEXT-KEY锁是MySQL中加锁的基本单位。实际就是间隙锁加行锁的组合。对一条数据行加NEXT-KEY锁,实际就是对该数据行加行锁和其前一个间隙加间隙锁。
比如对10加NEXT-KEY锁,为(5,10],间隙右边变成了闭区间,也就是加了一个行锁。

加锁实验详解

我们都知道MySQL有4个隔离级别:读未提交、读提交、可重复读、串行化。等会我们对这四个隔离级别逐一分析。

都哪些操作会引起加锁呢?
1、当前读:直接读取最新的数据,快照读是读取MVCC提供的快照,是不会加锁的。
2、更新操作
3、删除操作

我们都知道MySQL中的数据是以B+树也就是索引的方式存在的,对于不同的索引类型,加锁的方式也不一样。

MySQL中的锁是加在索引上的

所以我们从四个隔离级别,三种操作,和索引方面来逐一实践加锁原则。

首先看一下我们的表结构和数据

create table t(c1 int primary key, c2 int, c3 int, c4 int, unique index i_c2(c2), index i_c3(c3));

insert into t values (10, 11, 12, 13), (20, 21, 22, 23), (30, 31, 32, 33), (40, 41, 42, 43);

注意事项:
1、别忘了切换事务隔离级别。MySQL默认的隔离级别是可重复读。
2、别忘了手动开启事务,因为MySQL默认的是隐式事务,就是在语句执行前后自动的开启和提交事务。需要用start transaction来手动开启事务,通过commit 和rollback来提交和回滚事务。
3、我们可以使用MySQL自带的sql语句来查询当前存在的锁信息。以我使用的MySQL8.0.22为例,查询语句为select * from performance_schema.data_locks; ,不同的MySQL版本,查询语句可能有出入。

查询得到的锁信息如上图所示,其中有很多的列,我只截取了关键信息列:
1、INDEX_NAME:加锁的索引名字
1、LOCK_TYPE:加锁的类型,TABLE代表表锁,RECORD代表行锁。
2、LOCK_MODE:加锁的模式:
X代表NEXT-KEY的X锁
X,REC_NOT_GAP代表行X锁
X,GAP代表间隙锁
S,代表NEXT-KEY的S锁
S,REC_NOT_GAP代表行S锁
S,GAP代表间隙锁
3、LOCK_DATA:锁对应的数据,主键索引对应的为主键ID,非主键索引对应的是索引列数据和对应的主键ID。

1、读未提交

1.1、主键索引
1.1.1、主键索引上等值查询
a、查询的记录存在

start transaction;
select * from t where c1 = 10 for update;
select * from performance_schema.data_locks;
commit;

在这里插入图片描述
如上图所示,加了两个锁,一个是IX锁,一个加在主键上的c1=10的数据记录的行X锁


                
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值