MySQL事务详解(读写锁+MVCC)

本文详细介绍了MySQL事务的ACID特性,以及事务的四种隔离级别,重点讲解了InnoDB存储引擎中如何通过读写锁和MVCC实现事务隔离。内容涵盖了读写锁的工作原理,两种读取数据的方法——一致性非锁定读和锁定读,以及如何通过Read View和Undo Log实现MVCC。此外,还讨论了不同隔离级别下如何避免并发问题,如脏读、不可重复读和幻像读。

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


在数据库中,我们将我们的指定的一些操作设置为一个事务。

事务的四大特性ACID

  • Atomocity:原子性,一个事务中的所有操作,要么全部完成,要么全部失败。
  • Consistency:一致性,一个或多个事务执行后,原来一致的数据和数据库仍然是一致的。银行转账前后,金额的总额不改变。
  • Lsolation:隔离性,数据库开启一个事务时,不会受到其他事务对数据操纵导致数据不一致。多个事务并发互不影响。
  • Durability:持久性,事务提交后,对数据库中的数据的修改是永久的。它无法通过回滚或者任何方式来进行恢复。

而事务的ACID是通过InnoDB日志来保证。

  • 事务的隔离性是通过数据库锁+MVVC的机制实现的。
  • 事务的持久性通过redo log(重做日志)来实现。
  • 原子性和一致性通过Undo log(回撤日志)来实现.

Undo Log保存了事务发生之前的数据的一个版本,为了满足事务的原子性,在事务前备份数据到Undo Log,然后进行数据的修改。如果出现了错误或者用户执行了roll back语句,系统可以利用Undo Log的数据进行恢复。

Redo log确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

事务的隔离级别

MySql5.5后使用的是InnoDB引擎,它默认隔离级别是可重复读的

Orcale默认隔离级别是读已提交

MySQL数据库共定义了四种隔离级别

  1. Serializable(串行化):可避免脏读、不可重复读、幻像读取情况的发生。
  2. Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
  3. Read committed(读已提交):可避免脏读情况发生。
  4. Read uncommitted(读未提交):最低级别,以上情况均无法保证。

脏读:事务T1对数据进行修改,但未提交;事务T2进行数据读取,读取到修改但未提交的数据;事务T1进行回滚,取消了数据的修改,事务T2之前读取的数据为无效数据。

不可重复读:事务T2读取了一条数据,事务T1对此数据进行修改并提交,事务T2再次读取此数据。两次读取的数据不一致,数据被修改。

幻像读取:事务T1开启,事务T2对数据进行查询,得到结果一;事务T1对数据进行新增并提交,事务T2再次进行读取,得到结果二。结果二中比结果一中多出一条数据,就和幻像一样,称之为幻像读取,数据被增加。

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

不可重复读和幻像读的区别

  • 不可重复读是针对同一个数据项的概念,两个事务对同一个数据项进行了修改。
  • 幻像读时正对一个数据集的概念,在读取批量数据时,数据的数目发生了变化。

参考数据库的隔离级别

MVVC+读写锁

InnoDB的底层是使用读写锁(共享锁与排他锁)和MVCC(多版本并发控制协议)来进行隔离级别的实现的。它们的实现思想可以简单的做一个解释。

MySQL支持全部4个隔离级别,但在具体实现时,MySQL默认采用RR隔离级别,SQL标准是要求RR解决不可重复读的问题,但是因为MySQL通过next-key lock在RR隔离级别下解决了幻读的问题。

读写锁

首先InnoDB是基于读写锁的,也就是共享锁(S锁)排他锁(X锁)

在一个事务中,读取某条记录行,会为当前记录行添加一个读锁,其他事务也可以对该记录行添加读锁并且读取,但是不能添加写锁。而修改一条记录时,会为这个记录行添加一个写锁,其他事务不能为其添加任何锁。

  • 读锁是一个共享锁,它是对读锁共享的,但是不对写锁共享。
  • 写锁是一个排他锁,添加了写锁后就不能添加任何锁。
  • 读写锁都只有在事务结束后才会自动释放。

在InnoDB中实现了这种锁机制,并且基于这种锁机制使用了MVVC来提升读的效率

两种读取数据方法

在InnoDB中,基于MVVC在不同情况下,会有两种不同的读取数据的方式。

一致性非锁定读

在事务中,查询某一条数据时,这条数据正在被其他事务delete,update时,读操作并不会等待写锁的释放,而是去读取这条数据之前的快照版本,也称之为快照读

锁定读

在事务中,查询某条数据时,这条数据没有被其它事务delete,update时,这个时候读取的数据就是当前最新版本的数据,并且会为当前记录行添加读锁,这种读法也叫为当前读

此外delete,uptate,insert时的操作也存在着当前读的一种现象,但是会为记录添加写锁。

隔离级别的实现

基于上面的读写锁+MVVC,我们可以很容易的就实现RR(Repeatable Read)RC(Read Commited)

读未提交

这种不考虑逻辑性和安全性的隔离级别不会被使用,实现即不加任何锁即可。

读已提交

读已提交的实现在一致性非锁定读中,事务开始后,每次查询都会重新生成Read View,这样避免了数据脏读但却无法实现可重复读。

锁定读中,读取时会为当前记录添加写锁,其他事务无法获取写锁,不能对该记录行进行修改等操作,所以读未提交和不可重复读都不会发生。

可重复读

可重复读的实现在一致性非锁定读中,事务开始后,它只会为第一次查询生成Read View,这种方式实现了数据的可重复读。锁定读利用锁机制实现可重复读。

在InnoDB中的RR级别的隔离机制还实现了部分的避免幻读,在RR中,锁定读时,使用Next-key lock的方式来对查询范围内的间隙进行加锁,其他事务不能进行插入事务。一致性非锁定读不会发生幻读现象。

串行化:串行化就是让事务不能并发的执行,当有事务进行时,其他事务等待运行,虽然实现了高可靠,但是性能及其低下。

InnoDB的中MVVC原理

MVCC 的实现依赖于:隐藏字段、Read View、Undo log

在内部实现中,InnoDB 通过数据行的 DB_TRX_IDRead View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View 之前已经提交的修改和该事务本身做的修改。

隐藏字段

  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除
  • DB_ROLL_PTR(7字节): 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
  • DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引

Read View

Read View主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”,它是存储在事务中,我们所说的更新快照也就是更新它和undo log。

主要有以下字段:

  • m_low_limit_id:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于这个 ID 的数据版本均不可见
  • m_up_limit_id:活跃事务列表 m_ids 中最小的事务 ID,如果 m_ids 为空,则 m_up_limit_idm_low_limit_id。小于这个 ID 的数据版本均可见
  • m_idsRead View 创建时其他未提交的活跃事务 ID 列表。创建 Read View时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。m_ids 不包括当前事务自己和已提交的事务(正在内存中)
  • m_creator_trx_id:创建该 Read View 的事务 ID

Undo Log

undo log 主要有两个作用:

  • 当事务回滚时用于将数据恢复到修改前的样子
  • 另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现非锁定读。

参考InnoDB对MVCC的实现

使用事务

begin:事务开启

savepoint:定点回滚

rollback:回滚

begin;	//开启事务,或者说是关闭自动提交功能。
insert into test values(5,"lisi");
insert into test values(1,"1");
commit;	//提交事务,两个都被保存。
begin;	//开启事务
insert into test values(5,"lisi");
insert into test values(1,"1");
rollback;	//回滚,两个都未保存。不管语句是否正确。
begin;	//开启事务
insert into test values(5,"lisi");
savepoint a;	//设置一个定点a
insert into test values(1,"1");
rollback to a;	//回滚到定点a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值