【MySQL】七、MySQL的事务(一)

【MySQL】七、MySQL的事务(一)

本文主要介绍MySQL数据库的事务

7.1 回顾一下MySQL运行时多个事务同时执行是什么场景

平时我们执行增删改的时候,无非就是从磁盘加载数据页到buffer pool的缓存页里去,对缓存页进行更新,同时记录下来undo log回滚日志和redo log重做日志,应该的是事务提交之后MySQL挂了恢复数据的场景,以及事务回滚的场景。

平时我们一般都是写一个业务系统,然后业务系统对数据库执行增删改查操作,如下图:

image-20211008190034471

通常而言,我们都是在业务系统里会开启事务来执行增删改操作,例如下面的代码:

1
2
3
4
5
6
7
8
9
@Transactional
public void doService() {
// 增加一条数据
addUser();
// 修改一条数据
updateUser();
// 删除一条数据
deleteUser();
}

一般来说,业务系统是执行一个一个的事务,每个事务里可能是一个或者多个增删改查的SQL语句。一个事务里的SQL要不然一起成功就提交了,要不然有一个SQL失败,那么事务就回滚了,所有SQL做的修改都撤销了。

image-20211008190820483

接着问题来了,这个业务系统可不是一个单线程系统,是有很多线程的,因为他面向的终端用户是有很多人的,可能会同时发起请求,所以他需要多个线程并发来处理多个请求的。于是,这个业务系统是基于多线程并发的对MySQL数据库去执行多个事务的,如下图:

image-20211008191308553

7.2 多个事务并发更新以及查询数据,为什么会有脏写和脏读的问题?

业务系统往往都是多个线程并发执行多个事务的,对于数据库而言,他会有多个事务同时执行,可能这多个事务还会同时更新和查询同一条数据,所以这里会有一些问题需要数据库来解决,如下图:

image-20211008192137812

每个事务都会执行各种增删改查的语句,把磁盘上的数据页加载到buffer pool的缓存页里来,然后更新缓存页,记录 redo log和undo log,最终提交事务或者回滚事务,多个事务会并发干上述一些列事情。

如果多个事务要是对缓存页里的同一条数据同时进行更新或查询,此时会产生哪些问题呢?

这里实际上会涉及到脏写、脏读、不可重复读、幻读这四种问题。

先看第一个问题,脏写:

脏写的意思就是说有两个事务,事务A和事务B同时在更新一条数据,事务A先把他更新为A值,事务B紧接着就把他更新为B值,如下图所示:

image-20211008192944551

此时事务B是后更新那行数据的值,所以此时那行数据的值是不是B值?没错,而且此时事务A更新之后会记录一条undo log日志,因为在事务A更新之前,这行数据的值为NULL,所以此时事务A的undo log日志大概就是:更新之前这行数据的值为NULL,主键为XX

那么此时事务B更新完了数据的值为B,结果此时事务A突然回滚了,那么就会用他的undo log日志去回滚,此时事务A一回滚,直接就会把那行数据的值更新回之前的NULL值,所以此时事务A回滚了,可能看起来这行数据的值就是NULL了,如下图:

image-20211008193345116

然后就尴尬了,事务B查询的时候就会发现,他更新的B值没有了,就因为事务A反悔了就把数据值回滚成NULL了,搞得事务B更新的值也没了,这就是脏写。

所谓脏写,就是我刚刚明明写了一个数据值,结果过了一会儿却没了。他的本质就是事务B去修改了事务A修改过的值,但是此时事务A还没提交,事务A会随时回滚,导致事务B修改的值页没了。

那么什么是脏读呢?假设事务A更新了一行数据的值为A值,此时事务B去查询了一下这行数据的值,看到的值是不是A值?没错,此时如下图所示:

image-20211008193849466

现在事务B可能还没发现问题,拿着刚才查询到的A值做各种业务处理,但是此时事务A突然回滚了事务,导致他刚才更新的A值没了,此时那行数据的值回滚为NULL值。然后事务B紧接着此时再次查询那行数据的值,看到的居然是此时的NULL值,如下图:

image-20211008194123641

这就是脏读,他的本质就是事务B去查询了事务A修改过的数据,但是此时事务A还没提交,所以事务A随时会回滚导致事务B再次查询就读不到刚才事务A修改的数据了,这就是脏读。

无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据,因为另外一个事务还没提交,所以他随时可能会回滚,那么必然导致你更新的数据就没了,或者你之前查询到的数据就没了。

7.3 一个事务多次查询一条数据读到的都是不同的值,这就是不可重复读

不可重复读是这样的,假设我们有一个事务A开启了,在这个事务A里会多次对一条数据进行查询,然后呢,另外有两个事务,一个是事务B,一个是事务C,他们俩多事对一条数据进行更新的。

然后我们假设一个前提,就是比如说事务B更新数据之后,如果还没提交,那么事务A是读不到的,必须要事务B提交之后,他修改的值才能被事务A给读取到,其实这种情况下,就是我们首先避免了脏读的发生。

因为脏读的意思就是事务A可以读到事务B修改过还没提交的数据,此时事务B一旦回滚,事务A再次读就读不到了,那么此时就会发成脏读问题。

我们现在假设的前提是事务A只能在事务B提交之后读取到他修改的数据,所以此时必然是不会发生脏读的,但是此时就会发生另外一个问题,叫不可重复读。

假设缓存页里一条数据原来的值是A值,此时事务A开启之后,第一次查询这条数据,读取到的就是A值,如下图所示:

image-20211008195921825

接着事务B更新了那行数据的值为B值,同时事务B立马就提交了,然后事务A此时可是还没提交。事务A在执行期间第二次查询数据的时候,是可以查询到事务B修改过的值B的,如下图所示:

image-20211008200218007

紧接着事务C再次更新数据为C值,并且提交事务了,此时事务A在还没提交的情况下,第三次查询数据,查到的值为C值,如下图所示:

image-20211008200427350

那么上面的场景有什么问题呢?其实要说没问题页可以是没问题,毕竟事务B和事务C都提交之后,事务A多次查询到他们的修改值,是ok的。

但是要说有问题,也可以是有问题的,就是事务A可能第一次查询到的是A值,那么他可能希望的是在事务执行期间,如果多次查询数据,都是同样的一个A值,他希望这个A值是他重复读取的时候一直可以读取到的,他希望这行数据的值是可重复读的。

但是此时明显A值不是可重复读的,因为事务B和事务C一旦更新了值并且提交了,事务A会读到别的值,所以此时这行数据的值是不可重复读的。这个问题就是不可重复读的问题。

其实这个问题要取决于你的业务系统想要数据库是什么样子,如果你希望看到的场景就是不可重复读,也就是事务A在执行期间多次查询一条数据,每次都可以查到其他已经提交的事务修改过的值,那么就是不可重复读的,如果你的业务系统希望这样子,那也没问题。

但是如果你希望的是事务A开始执行的时候,第一次查询读到的是值A,然后后续你希望事务执行期间,读到的一直都是这个值A,不管其他事务如何更新这个值,哪怕他们都提交了,你就希望读到的一直是第一次查询到的值A,那么你就是希望可重复读的。

如果你希望的是可重复读,但是数据库表现的是不可重复读,让你事务A执行期间多次查询到的值都不一样,都是别的事务提交过的事务修改过的值,那么此时你就可以认为,数据库有问题,这个问题就是不可重复读的问题。

7.4 听起来很恐怖的数据库幻读,到底是个什么问题?

一个事务A,先发送一条SQL语句,里面有一个条件,要查询一批数据出来,比如”select * from table where id > 10”,类似这种SQL。

然后呢,他一开始查出来10条数据,如下图所示:

image-20211008201538889

接着这个时候,别的事务B往表里面插入了几条数据,而且事务B还提交了,如下图所示,此时多了几行数据出来

image-20211008201721175

接着事务A此时第二次查询,再次按照之前的一模一样的条件执行”select * from table where id > 10” 这条SQL语句,由于其他事务插入了几条数据,导致这次他查询出来了12条数据,如下图所示:

image-20211008201914943

于是此时事务A开始怀疑自己了,为什么一模一样的SQL语句,第一次查询是10条数据,第二次查询是12条数据?这就是幻读。就是指你一个事务用一样的SQL多次查询,结果每次查询都会发现查到了一些之前没看到过的数据,幻读特指的是你查询到了之前查询没有看到过的数据。

【MySQL】七、MySQL的事务(一)

https://www.shuiwh.com/posts/learn-mysql-007/

作者

水无痕

发布于

2025-07-18

更新于

2025-07-19

许可协议