当我们接触数据库系统相关的知识,或者涉及到电商、支付相关的系统设计,通常会听到"事务"这个词。
关于事务的定义
事务(Transaction) 是什么呢:
淳朴的理解,一系列操作,完成一件目标,事务就是这一系列操作的序列捆绑在一起的整体。
为什么需要事务这个定义呢:
我们看一个转账的过程:
有一堆转账请求: 张三给李四转100块,王麻子给张二嘎转50块
对于每笔转账请求,系统需要依次做如如下操作
1. 确认张三账户有至少一百块
2. 给张三的账户减少100块
3. 李四的账户增加100块
4. 这一条转账请求标记上,已处理
如果因为系统异常,4个操作任何一个没有写入成功,那账本一定会出错了,
所以,这4个操作,被我们定义为了一个事务
事务的四大特性
四个特性:原子性,一致性,隔离性,持久性
和其他一些文章不同,我们这里先明确,一致性是终极目标,而非手段,我们先来看一致性的定义:
(很多同学,包括我一开始觉得原子性和一致性是一回事,就是没有明白,一致性是目标,而非手段)
特性一: 一致性
定义:一旦事务的所有操作结束,事务就被提交。然后数据和资源将处于遵循业务规则的一致状态。
大白话就是说:涉及事务的系统,各种操作提交下,业务逻辑要正确
接下来,为了解决破坏一致性的若干问题,我们依次引入其他三个特性
问题一:前文提到的4个操作,操作到一半系统挂了,丢失了若干操作步骤,那么数据库的数据就出错了,一致性被破坏了
怎么解决这个问题呢,引入原子性的概念
特性二: 原子性
定义:事务是一个包含一系列操作的原子操作。事务的原子性确保这些操作全部完成或者全部失败。
然而,情况没那么简单:
问题二: 并发怎么办
假设同时两个转账请求过来了:A. 张三给李四转100块 B. 张三给王五转100块
两个线程同时处理两个请求
大部分软件工程师,大概都能看出有问题,线程会冲突,就会带来如下的若干问题:
A. 覆盖性问题
第一类更新丢失
A事务撤销时,把已经提交的B事务的更新数据覆盖了。
第二类更新丢失
B事务覆盖A事务已经提交的数据,造成A事务所做操作丢失。
B. 一致性问题
脏读:事务 A 读取了事务 B 未提交的数据,并在这个基础上又做了其他操作。
幻读:符合查询条件的数据集,在事务执行过程中发生了变化
例子: 需要给所有的用户赠送10个金币,需要以下几步:
1. 读出所有用户列表
2. 给这些用户增加10个金币
3. 提交事务
=> 问题: 在处理过程中,如果有新用户进来,他们得不到10金币的赠送,那么,本次事务操作操作不符合事务预期
不可重复读:在事务的执行过程中,已经读到的数据的值,发生了变化(可重复读:读多少次得到的数据都是一样的)
例子:
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,事务B把张三的工资改为8000,并提交了事务。
随后,在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致。
=> 问题:如果是类似于银行的对账系统,这类不一致性带来的问题会十分明显
幻读和不可重复读的理解:
我们操作一个事务,涉及数据集A, 在整个事务操作过程中,如果数据集A存在被其他事务提交修改的可能,则会产生读取不一致性
即使所有写入系统的数据,都是遵循原子性的事务操作完成;
然而读到的系统数据状态,和事务操作过程中面对的系统数据状态存在不一致的可能,导致事务操作不符合业务预期逻辑
更多冲突的具体场景参见: 参考资料【1】
我们很容易想到一个方法,那就是对所有数据整体加一把锁,一次只能执行一个事务~
Yes, 问题解决了,但是性能没了,一个事务可能要操作n步,假设耗时100毫秒,天哪,一天只能处理100万不到的交易量
为了解决一致性和性能的矛盾,前辈们引入了隔离性的概念,用不同级别的隔离性,解决并发和一致性、性能的矛盾
特性三: 隔离性
定义:引入的隔离机制,并发的事务操作,不会相互影响事务的正确性,进而避免事务的相互影响,破坏整个系统的一致性。
=> 对一个事务来说,除非它所涉及的数据是静态不变的,否则这个事务应该对所有数据无损的失败/回滚。
为了解决隔离性的需求,前人抽象出了4个隔离级别,本文先列出来,下一篇文章讲实现,那么对4种隔离级别,就更加清晰了
![](https://img.haomeiwen.com/i11614751/38f361fcc9186bf1.png)
特性四:持久性
持久性是比较好理解的,当我们执行一条 insert 语句后,数据库必须要保证有一条数据永久地存放在磁盘中,这个也算事务的一条特性。
持久性,更多的关注的是系统容灾性,不会因为底层系统的异常、崩溃、掉电导致操作的丢失
参考资料
【1】https://blog.csdn.net/lovesomnus/article/details/44459675
网友评论