MySQL的锁机制

本文详细解释了MySQL如何通过锁机制保证数据一致性,讨论了读-读、写-写、读-写/写-读并发情况下的处理,以及MVCC在解决脏读等问题中的应用。还介绍了MySQL的锁分类,包括共享锁(S锁)和排他锁(X锁)以及它们在事务中的作用。
摘要由CSDN通过智能技术生成

1.简介

MySQL的隔离性是由锁机制来保证的。锁是计算机协调多个进程或线程并发地访问某一资源你的机制。当多线程并发地访问某个数据时,尤其是在涉及金钱等安全敏感性数据的时候,需要保证数据在任意时刻最多只有一个线程可以对其进行修改,从而保证数据的一致性和完整性。

2.MySQL并发事务访问相同记录

并发事务访问相同记录可以大致分为三种情况:

2.1 读-读情况

读-读情况就是多个事务同时读取某条或多条相同的记录,由于读操作本身不会改变数据,因此是不存在任何数据安全问题的。

2.2 写-写情况

写-写情况即并发事务相继对相同的记录进行修改,在这种情况下会产生脏写的问题,任何一种隔离级别都不允许这种情况的产生,因此多个未提交的事务对同一条记录进行修改时,会让它们排队去执行,这个排队操作是通过锁来保证。这个所谓的锁其实是一个内存中的结构,在事务执行前本来是没有锁的,也就是说一开始是没有锁结构和记录进行关联的,如图所示:
在这里插入图片描述
当一个事物想要对这条记录进行改动的时候,首先会看看内存中有没有与这条记录关联的表结构,当没有的时候就会在内存中生成一个锁结构与之关联。比如事务T1需要对记录进行修改,就需要生成一个索结构与之关联,如下图:
在这里插入图片描述
在锁结构里有很多信息,为了简化理解,就拿两个比较重要的字段展示:

  • trx信息:代表这个锁结构是哪个事务生成的。
  • is waiting:表示当前事务是否在等待

当T1改动这条记录后,就会生成一个索结构与对应的记录进行关联,由于之前没有别的事务对该记录进行修改,所以is waiting是false。我们把这个场景称之为获取锁成功,之后就可以进行后续操作了。
在T1事务对该记录进行提交之前,事务T2也想对该记录进行修改,那么会先看看有没有锁结构与该记录进行关联,发现存在锁结构与该记录关联,那么T2会再生成一个自己的索结构与该记录进行关联,不过此时的is wating则为true,因为事务T2需要等待事务T1提交后才能对该记录进行修改。这个场景我们就称之为获取锁失败。如下图:
在这里插入图片描述
在事务T1提交后,会将与该记录关联的锁结构释放掉,然后查看是否有其他线程等待获取锁,发现T2事务在等待获取锁,会将T2锁结构的is wating改为false,且唤醒T2事务对应的线程,然后T2事务就可以正常执行了。如下图:
在这里插入图片描述

2.3 读-写情况

读-写或者写-读的情况,即一个事物在读取某条记录的时候,其他事务在对该记录进行修改,那么可能会产生 脏读、不可重复读、幻读的问题。MySQL在RR级别就解决了幻读的问题。

2.4 并发问题的读解决方案

怎么解决脏读、不可重复读、幻读的问题呢?有两种解决方案。

  • 方案一:读操作使用MVCC,写操作加锁
    所谓MVCC就是多版本并发控制,当进行常规的select操作(即简单的select 语句,不带for update或者lock in share mode)的时候,这时是快照读。此时在开启事务的时候会维护一个当前活跃事务(就是已开启但是还未提交的事务)列表-trx_list,里面的记录了当前活跃事务的id,由于事务的id是随着开启时间递增的,所以在最小事务id之前的事务都已经提交了,这些事务的id对于当前事务来说自然是可见的。最大事务id则是系统将要分配的下一个事务id,所以在最大事务id之后的事务对于当前事务来说都是未开启的,这些事务所做的修改对于当前事务来说自然是不可见的。同时记录在undo log中维护了各个事务对其修改的历史版本,所以根据事务id之间的关系就可以判断当前事务应该看到该记录的哪个事务版本的数据。由于当前读在开启事务的时候会生成一个ReadView,其中维护了上述的trx_list,且在本事务中多次进行查询都是用的最初的ReadView,因此在本事务中不论读多少次,某个记录的值都不会变,这就解决了脏读、不可重复读的问题。

  • 方案二:读、写都加锁
    在一些业务场景中,不允许读到记录的旧版本数据,每次都必须去读取记录的最新版本。比如在银行的存钱业务场景中,需要把用户当前的余额取出来,然后再加上本次存的钱,最后再把新的余额写到数据库中,这个过程中就不允许别的事务再读取用户的余额了。你可能会想为什么?想象一下这个场景,余额数据为100,取出来后,在根据本次存的钱50算出新的余额150的过程中,又来一个扣款的操作,假设扣款50,此时扣款的事务读到的当前余额还是100,那么在扣款事务的视角来看,扣完款的余额应该是100-50=50,扣款的操作也会再算完后把余额写回数据库。不论是扣款先写把余额改成50,然后存款后写再把余额改成150。或者是存款先写把余额改成150,然后扣款后写把余额改成50,最后的结果都是不对的。所以这种场景必须读也加锁,让读操作也排队执行。

3.MySQL的锁分类

从锁定粒度来划分,可以分为:表锁、行锁、页锁
从是否独占来划分,可以分为:共享锁(Shared Lock,简称S锁)、排他锁(Exclusive Lock,简称X锁)

3.1 共享锁(S锁)和排他锁(X锁)

共享锁是共享的,事务T1持有共享锁A的时候,事务T2也可以持有共享锁A。排他锁是排他的,事务T1持有排他锁B的时候,事务T2就无法获取排他锁B,只能排队等待,直到事务T1释放排他锁B后,事务T2才能获取到排他锁B。
X锁和X锁互斥,X锁和S锁也是互斥。S锁和S锁是不互斥的。举例说明,假设事务T1获取某个记录上的S锁,那么事务T2此时再想要获取该记录上的X锁是不可以的,需要等待事务T1释放记录上的S锁之后事务T2才可以获取记录上的X锁。当然事务T2若想要获取记录上的S锁,则是可以的。另外一种场景,假设事务T1获取了某条记录上的X锁,此时事务T2再想要获取该记录上的S锁或者X锁都不可能了,需要等待事务T1释放了X锁才可以。

3.2 读锁定

如果想要对读取操作进行加锁,有如下两种方式可以进行加锁:

3.2.1 共享读

select * from table lock in share mode;
语法如上,在常规的select语句后加上 lock in share mode,此时记录会被加上S锁。因为记录加上了S锁,此时允许其他事务对记录进行查询,但是进行对记录进行修改,因为增删改操作默认会加上X锁,由于记录上已经有S锁了,无法获取X锁,因此在 lock in share mode语句的事务提交之前,其他事务都不允许对记录进行修改。
操作示意图如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2.2 排他读

select * from table for update;
上面就是排他读,写法是在常规的select语句后加上 for update。这种情况在记录上加的则是X锁,在持有X锁的事务提交前,其他事务不仅无法修改该记录,连读取该记录都不行。
示例如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3 写操作

写操作主要有三种:insert、delete、update

3.3.1 delete

对一条记录做delete操作的过程,其实是先通过B+树找到这条记录,然后回去这条记录的X锁,再执行delete mark操作。

3.3.2 update

在对一条记录做update的时候分为三种情况:
在这里插入图片描述

3.3.3 insert

一般情况下,新操作操作不会加锁,通过一种称之为隐式锁的结构来保证插入的记录在本次事务提交前不会被其他事务访问。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值