【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

数据库事务的三个元问题

  • 2021-12-03
  • 本文字数:2904 字

    阅读完需:约 10 分钟

数据库事务的三个元问题

究竟什么是数据库的事务?为什么数据库需要支持事务?为了实现数据库事务,各种数据库是如何设计的?这次只谈理解,欢迎大家来讨论。

1. 数据库事务是什么


事务,就是用来保证数据操作符合业务逻辑要求而实现的一系列功能。换句话说,如果数据库不支持事务,上层业务系统的程序员就需要自己写代码,以保证相关数据处理逻辑的正确性。举个例子,数据库最开始普及就是在金融业,银行的存取款场景就是一个最典型的 OLTP 数据库场景,而事务就是用来保证类似场景的业务逻辑正确性的。


(图片来自网络,侵删)


原子性:如果你要给家人转账,必须在你的账户里扣掉 100 块,在家人账户里加上 100 块,这两笔操作需要一起完成,业务逻辑才是正确的。但是程序在做修改时,肯定会有先后顺序,试想一下程序扣了你的钱,这个时候程序崩溃了,家人账户的钱没有加上,那这 100 块是不是消失了?你是不是要发疯?那么,就把这两笔操作放进一个事务里,通过原子性保证,这两笔操作要么都成功,要么都失败。这样才能保证业务逻辑的正确性。


一致性:有很多文章讲过一致性,但是很多人会把一致性跟原子性混在一起说。事务的一致性指的是,每一个事务必须保证执行之后所有库内的规则依旧成立,比如内外键、constraint、触发器等。举例来说,你在储蓄卡里有 100 元,理财账户里有 100 元,基金账户有 100 元,那么你在资产总和里会看到 300 元,这 300 元必须是三个账户余额加在一起得到的。你从储蓄卡里转出去了 100 元给家人,那么可以在数据库上创建触发器,当储蓄卡余额账户减 100 元的同时,把资产总和也同步减去 100 元,不然就会出现逻辑上的错误。你已经转走了 100 元储蓄卡余额,实际资产总和应该是 200 元,若还是 300 元,数据库状态就不一致了。因此实现事务的时候,必须要保证相关联的触发器以及其他内部规则都执行成功,事务才算执行成功。如果在减去资产总额时出错,数据库就会进入不一致的状态那么这笔转帐交易也不能成功。


那么一致性跟原子性的区别到底在哪里呢?原子性是指多个用户指令之间必须作为一个整体完成或失败,而一致性更多是数据库内的相关数据规则同时完成或失败。


持久性:事务只要提交了,对数据库的修改就会保存下来不会丢了。简单来说,只要提交了,数据库就算崩溃了,重启之后你刚存的 100 块依然在你的账户里。


隔离性:每个事务相对于其他的事务有一定独立性、不能互相影响,因为数据库需要支持并发的操作来提高效率。在并发操作时,一定要通过操作之间的隔离来保证业务逻辑的正确性。比如,你转帐 100 块给家人,一系列操作的最后一步可能是输入验证码,这个时候转帐还没有完成,但是在数据库里,你的账户对应的记录中已经减去 100 块,家人账户也加了 100 块,就等着验证码输入以后,事务提交,完成操作。那么,这个时候,家人通过手机银行能够查到这 100 块么?你的答案可能是不能,因为你的转帐操作还没有提交,事务还没有完成,那么数据库就应该保证这两个并发操作之间具有一定的隔离性,这样才符合业务逻辑。


到底应该隔离到什么程度呢?隔离性又分为 4 个等级:由低到高依次为 Read uncommitted(读未提交)、Read committed(读提交)、Repeatable read(可重复读取)、Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。


怎么理解不同的隔离等级呢?首先要理解并发操作,并发操作就是指有不同的用户同时对一个数据进行读、写操作,那么在这个过程中,每个用户应该看到什么数据才能保证业务逻辑的正确性呢?


如果是前面存取款的场景,我看到的是已经存进来的钱,也就是必须是已经提交的事务。而 12306 刷火车票,你可以看到有 10 张余票,但是在下单的时候告诉你票卖完了,因为同时有 10 个用户把票买掉了,你需要重新刷余票。这个也是可以接受的。也就是说我可以读到一些虚假的余票,在业务上也没有什么问题。那么在设计这两个不同系统时,就可以选择不同的事务隔离级别来实现不同的并发效果。不同的隔离等级就是在系统的并发性和数据逻辑的严谨性之间做出的平衡。

2. 数据库如何实现事务


数据库实现事务会有多种不同的方式,但基本的原理类似,比如都需要对事务进行统一的编号处理,都需要记录事务的状态(是成功了还是失败了),都需要在数据存储的层面对事务进行支持,以明确哪些数据是被哪些事务插入、修改和删除的。同时还会记录事务日志等,对事务进行系统化的管理以实现数据的原子性、一致性和持久性。


要实现事务的隔离性,最基础的就是通过加锁机制,把并发操作适当串行化来保证数据操作的正确逻辑。但是为了要保证系统具有良好的并发性能,必须要在实现事务隔离性时找到合理的平衡点。


大部分数据库(包括 Oracle、MySQL、Postgres 在内)在做并发控制的时候,都会采用 MVCC(多版本并发控制)的机制来保证系统具有较高的并发性。不同数据库实现 MVCC 的具体方案不尽相同,但其基本原理类似。

3. MVCC 实现原理


所谓 MVCC,就是数据库中的同一查询根据相关事务执行的先后顺序以及隔离级别的不同,可能会存在不同版本的结果,通过这样的手段来保证大部分查询操作不会被修改操作阻塞并保证数据逻辑的正确性。简单来说就是,用存储空间来交换并发能力。


下面以 Postgres 为例介绍一下 MVCC 的一种实现方式,下图用以解释 Posrgres 里最基本的数据可见性是如何实现多版本控制的。


(图片来自网络,侵删)


首先,Postgres 里的每一个事务都有编号,这里可以简单理解为时间顺序编号,编号越大的事务发生越晚。然后,数据库里的每一行记录都会保存创建这条记录的事务号(Cre),也会在记录删除时保存删除这条记录的事务号(Exp),换句话说,只要 Exp 这里一列里记录了事务编号,就说明这条记录被删除了。那么一个事务应该能看见哪些记录呢?Postgres 里每一个事务都会保存一个当前系统的事务快照(Snapshot),这个快照里会保存事务创建时当前系统的最高(最晚)事务编号,以及目前还在进行中的事务编号。在如上图所示的一个事务的快照里,最高事务编号为 100,目前正在进行的事务有 25、50 和 75。对应左边数据记录,这 6 行数据的可见性就如同标注的一般:


  • 第一行,Cre 30,没有删除,在 100 这个时间点,应该能看到。

  • 第二行,Cre 50,没有删除,但是 50 这个事务还没有提交,正在进行中,所以看不见。

  • 第三行,Cre 110,没有删除,但是 100 这个时间点 110 事务还没有发生,所以看不见。

  • 第四行,Cre 30,Exp 80,在 80 的时候数据被删掉了,所以看不见。

  • 第五行,Cre 30,Exp 75,在 30 的时候被创建,75 时候被删掉了,但是 75 这个事务在 100 的时候还没有提交,这条记录在 100 的时候还没有删掉,所以看得见。

  • 第六行,Cre30,Exp 110,在 30 的时被创建,110 时候被删掉,但是在 100 时候,110 还没有发生,所以看得见。


综上就是这个事务对这六条记录的可见性,也就是一个数据版本。大家可以看一下,如果另一个事务的快照里存的是最高事务编号为 110,正在进行的事务为 50,那么它能看到的数据应该是哪几行呢?


同时大家也看到,Postgres 里删除一行数据就是在这一行的 Exp 这个列记录一个删除事务的编号。相当于做了一个删除标记,而数据没有真正被删除,因此 Postgres 数据库需要定期做数据清理操作(Vacuum)。我们这里假定所有的事务最终都是正确提交了,Postgre 在现实场景里的可见性比介绍的要复杂,会存在某些事务没有提交的情况,这里不再展开。

2021-12-03 10:291162

评论

发布
暂无评论
发现更多内容

这些智能合约漏洞,可能会影响你的账户安全!

创意时空

IDEA配置tomcat

楠羽

#开源

Network源码接口分析

长安链

rocksdb和innodb的一些区别

趁早

低代码是什么?国内排名前 5 的低代码开发平台对比

蒋川

低代码 开发工具 开发平台

区块链交易隐私如何保证?华为零知识证明技术实战解析

创意时空

【JVM】HotspotJVM精通垃圾回收器原理

小明Java问道之路

8月月更

技术解析+代码实战,带你入门华为云政务区块链平台

创意时空

构建万物可信的基石:解密区块链跨链技术

创意时空

一起学习设计模式:责任链模式

宇宙之一粟

设计模式 8月月更

iofod导入任意前端资产,以 Element UI 为例

iofod jude

小程序 前端 低代码 网页

基于Vue3常用代码块

青柚1943

typescript Vue3 Element Plus Pinia sortablejs

【JVM】HotspotJVM 分代回收机制

小明Java问道之路

8月月更

C/CPP基础练习题多维数组,矩阵转置,杨辉三角详解

CtrlX

c c++ 基础 8月月更

每日一R「21」Unsafe Rust

Samson

学习笔记 8月月更 ​Rust

【算法实践】一天路走到黑--手把手带你实现坚持不懈的线性查找

迷彩

Python 数据结构 算法实践 8月月更 线性查找

Java进阶(一)内存解析

No Silver Bullet

Java 9月月更 内存解析

[Go WebSocket] 为什么我选用Go重构Python版本的WebSocket服务?

HullQin

Go golang 后端 websocket 8月月更

影视动漫制作为什么要选择云渲染农场?

Finovy Cloud

计算器 云渲染 影视渲染

Go 代码城市上云——KusionStack 实践

SOFAStack

开源

【编程实践】认识爬虫并手把手带手实现新闻网站的爬取

迷彩

记录 Python爬虫 8月月更 网络爬虫

金融科技创新者的困境

木风

金融科技 数字化转型 科技创新

[教你做小游戏] 展示斗地主扑克牌,支持按出牌规则排序!支持按大小排序!

HullQin

CSS JavaScript html 前端 9月月更

创投基金黑钻资本Black3Lab Capital主投互联网3.0

股市老人

自然语言处理--神经网络的复习

IT蜗壳-Tango

自然语言处理 nlp 9月月更

艺术收藏NFT系统开发:NFT功能搭建

开源直播系统源码

数字藏品 数字藏品系统软件开发 数字藏品开发

阿里云高性能计算负责人何万青:阿里云大计算加速HPC与AI融合

阿里云弹性计算

AI HPC 高性能计算 无影云电脑 计算巢

玩转KubeEdge保姆级攻略

乌龟哥哥

8月月更

微服务网关Gateway实践总结

Java 架构

电商订单全流程可观测性最佳实践

观测云

创投基金黑钻资本Black3Lab Capital主投互联网3.0

EOSdreamer111

数据库事务的三个元问题_文化 & 方法_Zilliz_InfoQ精选文章