数据库锁 与 事务隔离级别

本文详细介绍了MySQL中的MVCC(多版本并发控制)和锁机制,如何解决并发事务中的脏读、不可重复读、幻读问题。通过分析事务的隔离级别,如读未提交、读已提交、可重复读和可串行化,阐述了各种锁类型(如S锁、X锁、表锁、行锁)的作用和相互关系。MVCC通过快照读和当前读确保数据一致性,而锁则在事务操作前后对数据进行锁定,防止并发问题。文章还讨论了不同隔离级别下锁的使用,以及如何通过设置事务隔离级别来避免并发问题。

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

在马士兵教育的面试突击班里,有对不同隔离级别加什么锁的介绍

课程地址:马士兵教育官网 - IT职业领路人 (mashibing.com)

同时必须要研究MVCC:无锁的方式解决数据库并发事务的读-写问题。

这可真是一个大课题,MVCC与锁、事务之间的关系。

【MySQL笔记】正确的理解MySQL的MVCC及实现原理_SnailMann的博客-CSDN博客_mvcc原理

数据库事务的隔离级别,正是MVCC配合锁的使用,解决了脏读、不可重复读。 能解决幻读吗?可以的,只有快照读的时候才会使用MVCC,当前读是不会使用的。

MVCC(多版本并发控制)简单流程总结:

  1. 多版本怎么来的:在事务中,修改或删除(不包括create)一条记录的同时(具体执行到修改语句的时候),会为其在undo log中保存一下当前数据(一个版本),多次修改的话,则这条记录会有多个版本
  2. 快照读怎么读取多版本:第一条说的是在事务中对数据进行修改或删除,产生了多个版本。如果有另外有个事务去快照读这条记录,会读到哪个版本的记录数据?在执行快照读读取这条记录的时候,会为其创建一个read view(决定了本事务的可见性),里面记录了当前活跃的事务(包括发起快照读的这个事务),然后拿当前数据库中当前这条记录中的事务ID(叫做DB_TRX_ID,不是发起快照读的这个事务,我认为只有事务成功提交才能在数据库记录中添加成功事务的ID,记住这条,对于理解MVCC的快照读很重要),去与read view中记录的活跃事务ID做比较(事务根据时间,ID号会单向变大),如果找不到合适的便跟undo log中各个版本记录的事务ID做比较,最终找到快照读能够获取的记录。在这个过程中解决了并发事务的读-写问题。

 该视频介绍MVCC,讲的不错:

【IT老齐030】这可能是最直白的MySQL MVCC机制讲解啦!_哔哩哔哩_bilibili

数据库中有读锁(S锁)、写锁(X锁)、表锁、行锁等。S锁与X锁是互斥的,在读的时候不能写,写的时候不能读。表锁和行锁等其实指的是S锁和X锁的作用域,S锁和X锁是作用在行、页、还是整张表。

参考文章:MySQL 表锁、行锁、间隙锁、页锁介绍分析_时光、疏离了记忆づ童话的博客-CSDN博客_mysql页锁和间隙锁

  1. 表锁:开销小,加锁快,一般不会出现死索,但是影响并发性能,并发性能差。系统升级,数据迁移时使用表锁
  2.  行锁:开销大,加锁慢,可能出现死锁,但是对并发性能影响小,并发性能好。

默认的select语句有锁吗?这个得看数据库引擎的事务的隔离级别。

并发过程中会出现的问题:

更新丢失(比较特殊),脏读,不可重复读,幻读这4种并发一致性问题,是由数据库提供一定的事务隔离机制来解决。

Note:其中任何的事务隔离级别,都可以解决更新丢失问题,但是这里的更新丢失很特别,并不是两个事务都成功导致的数据互相覆盖,而是其中一个事务修改数据后成功提交,另外一个事务失败回滚,导致回滚后的数据覆盖成功事务的数据。

更新丢失分为两种情况,具体请看这篇文章(写得不错):仅此一文让你明白事务隔离级别、脏读、不可重复读、幻读 - 李玉宝 - 博客园 (cnblogs.com)

  1. 第一类丢失更新(Lost Update)
    在完全未隔离事务的情况下,两个事务更新同一条数据资源,某一事务完成,另一事务异常终止,回滚造成第一个完成的更新也同时丢失 。这个问题现代关系型数据库已经不会发生,就不在这里占用篇幅,有兴趣的可以自行百度。 
  2. 第二类丢失更新 
    两个事务都提交成功,但是A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失,在事务的最高隔离级别可串行化(Serializable)可以解决,它通过强制事务排序,使之不可能相互冲突,从而解决幻读第二类更新丢失问题。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常数据库不会用这个隔离级别,我们需要其他的机制来解决这些问题:乐观锁和悲观锁

+------------------------------+---------------------+--------------+--------------+--------------+
| 隔离级别                      | 读数据一致性         | 脏读         | 不可重复 读   | 幻读         |
+------------------------------+---------------------+--------------+--------------+--------------+
| 读未提交(Read uncommitted)    | 最低级别            | 是            | 是           | 是           | 
+------------------------------+---------------------+--------------+--------------+--------------+
| 读已提交(Read committed)      | 语句级              | 否           | 是           | 是           |
+------------------------------+---------------------+--------------+--------------+--------------+
| 可重复读(Repeatable read)     | 事务级              | 否           | 否           | 是           |
+------------------------------+---------------------+--------------+--------------+--------------+
| 可序列化(Serializable)        | 最高级别,事务级     | 否           | 否           | 否           |
+------------------------------+---------------------+--------------+--------------+--------------+

通过事务的隔离级别就能解决上面的问题。事务是通过 MVCC+锁 的方式去解决上面的问题,在事务操作前给受影响的记录加锁,等事务结束的时候释放锁。那么是如果通过S锁和X锁来实现的呢?

怎样查询事务隔离级别和设置事务隔离级别呢?

MySQL的四种事务隔离级别 - 带着梦逃亡 - 博客园

事务的隔离级别与锁的关系: 

1.没有隔离级别

(不加事务???不对,应该是开启了事务,但是没有隔离级别,就是说完全未隔离事务),这个时候会发生更新丢失问题: 两个线程操作同一条数据,对其进行更新操作,更新会被覆盖(或者,两个事务更新同一条数据资源,某一异常事务终止,回滚造成第一个完成的更新也同时丢失。)。怎么解决这个问题呢? 应该要通过加锁去解决问题,任何隔离级别都解决了这个问题。

2.Read UnCommit级别(读未提交):

这个级别肯定解决了更新丢失的问题(通过加什么锁解决的? ??这个结论有待确认),但是还有脏读的问题。事务A去更新数据,另外一个并发事务B去读数据,事务B读到了事务A中没有提交的数据(被回滚了),这就是脏读。怎样解决事务B的脏读问题?通过加读锁(S锁)吗? 这其实就是两个并发事务,一个去读,一个去写,才造成了这个问题。

脏写是怎样解决的?锁是怎样加的呢?脏读是怎样造成的呢?

这个时候由于写操作对应的事务加了S锁,所以这个时候,其它并发事务不能再写???

只是在写的时候加了S锁,这样的话,其它任何事务都可以在任何时候读取这个数据,因为可以任意获取S锁,这就造成了脏读。 

3.Read  Commit:

这个级别解决了脏读的问题(通过加S锁解决的???),但是还是有问题,那就是不可重复读的问题。事务A去更新数据,另外一个并发事务B在更新前去读取数据,然后在事务A提交后,再去读取数据,事务B两次读取的数据不一致,这是不可重复读问题。怎样解决???通过加读锁(S锁)吗? 这其实也是两个并发事务,一个去读,一个去写,才造成了这个问题。

怎样解决脏读问题的?

读操作的事务,在读的瞬间加了读锁(S锁,当前读,不会读到旧版本的数据),读完之后立即释放,这样在读的时候就不能写;写事务整个加了X锁,这样在写事务的整个执行过程中,别的事务都不能读,防止了其它事务读取未提交的数据,这样就解决了脏读问题。 读与写两方一起防止脏读。疑问:难道不是通过MVCC的方式(不加锁)的方式解决了读写并发问题(此处是指脏读)吗?

为什么又会有不可重复读和幻读问题? 

4.Repeatable Read: 

解决了不可重复读的问题(通过加S 锁实现的????), 但是这个级别还有幻读的问题。事务A要去增加一条数据,并发事务B在A增加前没有读到这条数据,然后事务A增加这条数据,然后事务B读到了这条数据,这条数据对事务B来说,就是突然出现了,这就叫做幻读。另外一种解释,事务B将表清空,然后事务A又插入一条数据,然后事务B发现表中又有数据,造成幻读。怎样解决???通过加读锁(S锁)吗?  这其实也是两个并发事务,一个去读,一个去写,才造成了这个问题。

怎样解决不可重复读问题?

读的时候要全程加读锁(S锁),这样的话,在整个事务过程中,任何其它事务不能写,这样的话,就可以保证两次读到的数据是一致的。当然了,写操作要加写锁,不然的话任何事务都可以做写操作。

5.串行化(Serializable): 可以解决幻读问题,是通过加写锁(X锁)的方式,就是直接给被操作记录加X锁,实现了串行化。但是这样会让事务串行执行,极大的降低并发性能。

为什么会出现幻读?

因为前面加的锁都是行锁,都是针对一行数据的操作,而幻读的问题要放眼于整张表,而不是具体某一行,例如事务A清空某张表,操作的是整张表,而不是具体某一行,这时候行锁对于整张表的操作没有意义,并发事务B可以不受任何限制(肯定不受行锁限制,因为我要插入新的一行,哪里来的行锁???)的随便插入一条数据,事务A再查询的时候,发现多了一条数据,这样就造成了幻读。

怎样解决幻读?

在读的时候,整张表加读锁(S锁)

在写的时候,整张表加写锁(X锁)

By default, InnoDB operates in REPEATABLE READ transaction isolation level. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 14.7.4, “Phantom Rows”). 是使用next-key锁去解决幻读问题,但是这里为什么说是在RR隔离级别下呢?

小总结:脏读、不可重复读、幻读,都是由于并发事务中,一个事务去写数据,一个数据去读数据,脏读和不可重复读是针对具体某行的操作,所以需要加行锁;幻读是针对整张表的操作,所以需要加表锁。

其它知识点:

事务的ACID:

其中的隔离性就是通过MVCC+锁的方式实现的 

ACID中的D是使用redo log实现的。

数据库恢复:

分为两种情况:

1.删库跑路(磁盘数据丢失)

这时候应该使用全量备份数据+binlog去恢复磁盘中被删除的数据,还要结合redo log去恢复数据库被删到发现数据数据被删除这段时间的数据。举例说明:

数据库在上午9点被删除,10点才发现这个情况,全量备份是每天凌晨1点做的(全量备份数据);1点到9点的数据(这段时间数据库还能正常操作),其操作逻辑被放到了binlog(使用binlog恢复);9点到10点这段时间数据库已经不能正常工作,但是新增的数据被放到了缓冲池中中,将缓冲池中数据输入磁盘数据库会失败,但是redo log会记录下对缓冲池物理日志,所以可以通过redo log恢复。

2.数据库崩溃(缓存池中数据丢失)

这时候应该是redo log去恢复数据,redo log会记录下对缓冲池的逻辑操作,所以可以通过redo log恢复。我们新增或者修改的数据,在commit(事务提交)之后,可能只是在缓冲池中进行,没有立即同步到磁盘数据库,那么当然有可能丢失,但是数据库也对缓存操作提供了一种持久化机制,这就是redo log。

3.binlog与redolog的区别(重要)

redolog记录的是对缓冲池的操作,而且存储的是物理日志(就是说redo log是存储在磁盘中的)。ACID中的D是使用redo log实现的。

而binlog记录的是对磁盘数据库的操作,存储的是逻辑日志(DDL,DML)。

4.修改一条数据库记录的流程: 

redo log记录的是对缓冲池的操作(也是事务发起后的操作),但是log本身是存储在磁盘中的。而undo log记录的是增删改之前的记录,用于事务失败的回滚,保证原子性。

仔细阅读下面的两篇文章:

Mysql的select加锁分析 - wintersoft - 博客园

【原创】MySQL(Innodb)索引的原理 - 孤独烟 - 博客园

Mysql 索引优化分析 - ITDragon龙 - 博客园 (cnblogs.com)

MySQL 表锁、行锁、间隙锁、页锁介绍分析_时光、疏离了记忆づ童话的博客-CSDN博客_mysql页锁和间隙锁

事务失效及解决方案:

​​​​​​SpringBoot使用@Transactional_flyaway86的博客-CSDN博客_springboot transactional

SpringBoot事务隔离等级和传播行为 - zincredible - 博客园 (cnblogs.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值