聊聊数据库本地事务

本文探讨了数据库本地事务中的ACID特性,重点分析了原子性、一致性和隔离性的实现,包括Commit Logging、Shadow Paging和Write-Ahead Logging等方法,并介绍了多版本并发控制(MVCC)对隔离性的优化。通过理解这些概念,有助于更好地掌握数据库事务的运作原理。

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

之前有个朋友问我,数据库如何保证的一致性?

我:这么小白的问题都不知道??

于是就绝交了。。


如果在分布式环境下讨论事务还是比较复杂的。

所以,先从本地事务开始聊一波。所谓本地事务,就是单数据源的事务场景。

事务的 ACID 都知道,分别是:

原子性(Atomicity):事务包含的所有操作要么全部成功,要么全部失败回滚到事务前的状态。

一致性(Consistency):事务前后的状态都是一致的,比如A给B转账,转账前两人账户总和是1千元,那转账之后也得是1千元。

隔离性(Isolation):事务之间相互隔离,数据读写不会互相干扰。

持久性(Durability):事务一旦成功提交,数据将会被修改,不会丢失。

需要注意的是,这4个特性,C 是目的,AID 都是手段!!

所以,重点需要分析的是 AID,

其中 AD 是紧密相连的,没有持久性就没有原子性。

持久化主要依赖磁盘,而写磁盘这个操作本事不是原子的,比如,可能存在写了一半机器断电的情况,这对一个事务来说就是中间的一种状态了。

一般解决办法有两种:

Commit Logging

首先,事务提交时需要记录更多的信息,比如数据修改前是什么,修改后是什么,数据在磁盘的哪个位置等等

其次,标记状态,标记事务当前处于什么状态,“已提交”还是“已提交未完成”

最后,故障恢复时根据前面记录的信息就可以对事务进行恢复了。

Shadow Paging

修改数据时,先 Copy 一份影子数据去修改,修改完成后再修改数据指向的位置,这步操作本身就是原子的。但是这种方案并发能力比较有限,所以一般不会使用。

Commit Logging 也是有问题的,它的问题在于,只有事务提交了并且标记好“已提交”的事务状态后才允许实际修改数据。

这个问题就大了,这是一种阻塞式操作,对数据库的性能影响很大。

于是就得想办法解决这个问题,仔细想想,Commit Logging 之所以有上述限制,也是可以理解的,如果允许提前修改数据,数据库崩溃后这个被修改的数据就成为错误数据了。

那假如有办法让这个被修改的数据回滚到被修改前的状态,这个问题就解决了。

没错了,这个就是 Write-Ahead Logging 的解题思路了。

在 Write-Ahead Logging 里,Commit Logging 记录的日志叫做 Redo Log,个人觉得翻译成续做日志更合适点。

在这个基础上又加了一个Undo Log,回滚日志,就是为了解决刚刚 Commit Logging 存在的那个问题。

有了 Undo Log,就可以提前修改数据,不用担心找不到回家的路了。

Write-Ahead Logging 在崩溃恢复时会经历三个阶段,

首先,看看哪些事务是没有正常完成的

然后,对其中那部分已提交但没有正常完成的事务,根据 Redo Log 执行续作。

最后,剩下的那部分就是需要回滚的事务了,根据 Undo Log 执行回滚。

上面说的是数据库如何实现的原子性和持久性,继续看看数据库如何实现的隔离性

这个需要锁来保证,一般数据库提供了三种锁:

锁类型说明
写锁也叫排它锁,如果一个事务加了写锁,那其它事务既不能加写锁,也不能加读锁。就是告诉其它事务,坑我已经占了,你再等等。
读锁也叫共享锁,如果一个事务加了读锁,其它事务也可以加读锁,但是不能加写锁。就是告诉其它事务,是兄弟(读锁)就过来,不然(写锁)就等等吧。
范围锁范围锁与其它锁的区别在于,一般锁锁住的是具体几行记录,而范围锁锁住的则是一个范围,比如 amount < 10 的这个区间被锁住后,这个区间不仅不能修改数据,也不能增删数据。

数据库的隔离级别一般会提供多种,方便使用者根据实际情况进行调整。

隔离级别问题
可串行化
通过加范围锁避免幻读
可重复读
加上读锁不释放(贯穿事务生命周期),阻塞其它事务的写锁,从而避免不可重复度问题
幻读
在当前事务里,可能被其它事务的增删影响到,两次读到的记录数量不一致
读已提交
加上读锁,读完释放,所以无法解决可重复读问题
不可重复读
在当前事务里,可能被其它事务的修改影响到,两次读到的数据状态不一致
读未提交
因为不加读锁,不受约束,另一个事务加上写锁未提交的数据也能读到
脏读
在当前事务里,可能读取到另一个事务未提交的数据

上面说的都是一个事务读被另一个事务写影响到隔离性的情况,针对这种情况,有一种“多版本并发控制”的优化方案。

MVCC 就是说,现在一条记录有多个版本了,这个版本号用的是事务ID,是全局有序递增的。

如果是可重复读的隔离级别,读版本号小于等于当前事务id的最大的那条数据就行了,

如果是读已提交的隔离级别,读版本号最大的那条记录。

需要注意,MVCC 是对读+写的优化,对其它场景不适用。

有了 AID 的保证,C 其实就不是问题了。


从此,友谊的小船又可以扬帆起航了。。

欢迎关注我微信公众号《倔强的文哥》(一个表面冷酷,内心热乎的互联网码农),不定时分享各种Java技术经验、面试热题、Python实用小技巧。  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值