事务
文章目录
小结
- 原子性 / 隔离性
- 脏读 / 不可重复读 / 幻读
- 事务隔离级别: 串行化 / 可重复读 / 读已提交 / 读未提交
概念
问题一:
小明在线转账, 从 A 银行转账 100 元到 B 银行, 对应的数据库操作是
1: update A set amount=amount-100 where no='小明' -- 从A银行减去100元
2: update B set amount=amount+100 where no='小明' -- 从B银行加上100元
如果步骤1 已完成, 由于某种原因, 程序挂了, 则小明 A 银行账户上已扣减100元, B银行账户却没有加上100元
再想象下, 如果这个过程中操作了非常多的表, 当某一个环节出了问题, 那么整个数据将会处于混乱的状态
为了解决这种问题, 我们引入了原子性的概念
原子性
原子性: 原子性是指事务是一个不可再分割的工作单元,事务中的操作要么都执行,要么都不执行,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
问题二:
A有一个招商银行北京分行的账户, 余额分别是10,000
A在银行柜台上从北京分行转账 100 给 B, 操作如下:
-- A扣100
update account = account - 100 where no = 'A'
-- 检查 B 账号的合法性, 若合法, 则继续执行; 若非法, 则回滚整个代码
-- xxx(代码略)
-- B 加 100
update account = account + 100 where no = 'B'
同时 A 在 手机上查看自己的余额, 代码如下:
select account where no = 'A'
这个时候, 由于是同时操作, 这些语句的执行顺序有多种组合, 若是以下组合, 会有什么问题?
执行次序 | 语句说明 | 语句 | 结果 |
---|---|---|---|
1 | A扣100 | update account = account - 100 where no = ‘A’ | |
2 | 检查 B 账号的合法性 | (代码略) | |
3 | A 在手机上查看余额 | select account where no = ‘A’ | 查到余额是多少? |
4 | B 加 100 | update account = account + 100 where no = ‘B’ |
- 若B 账户合法, A 在手机上查看余额, 余额是 9,900, 正确无误
- 若B 账户非法, A 在手机上查看余额, 余额是 9,900, 从A的角度来看会非常困惑, 明明没有转账成功, 为什么依然会将卡里的钱扣了
这种情况我们称之为 脏读
脏读
一个事务读到另外一个事务还没有提交的数据,我们称之为脏读。
问题三:
将问题二稍微改动: A 在柜台上给 B 转账的同时, 在手机上查看余额, 并且查看了两次
执行顺序可能如下:
执行次序 | 语句说明 | 语句 | 结果 |
---|---|---|---|
1 | A 在手机上查看余额 | select account where no = ‘A’ | 查到余额是多少? |
2 | A扣100 | update account = account - 100 where no = ‘A’ | |
3 | A 在手机上查看余额 | select account where no = ‘A’ | 查到余额是多少? |
4 | 检查 B 账号的合法性 | (代码略) | |
5 | B 加 100 | update account = account + 100 where no = ‘B’ |
当B账户不合法时
在第一次查到的余额是 10000, 正确,
第二次查到的余额是 9900, 从A的角度来看会非常困惑, 明明没有转账成功, 为什么依然会将卡里的钱扣了
这个例子不是特别严谨, 因为两次读并不是在同一个事务里
不可重复读
一个事务先后读取同一条记录,但两次读取的数据不同,我们称之为不可重复读。
问题四:
A在柜台上新开了一个广州招行的账户, 并往里存了100元, 同时, A在手机上查看所有招行卡的余额
代码执行顺序可能如下:
执行次序 | 语句说明 | 语句 | 结果 |
---|---|---|---|
1 | 开通广州招行的账户 | 代码略 | |
2 | 往广州招行的账户存100元 | update account = account - 100 where no = ‘A_GZ’ | |
3 | A 在手机上查看所有招行卡余额 | select account where no in (‘A’, ‘A_AZ’) | 查到余额是多少? |
4 | 通知广州招行等操作 | 代码略 |
当新开了广州招行的账户失败时, 由于查到的余额是 11000, 故错误
幻读
一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读。
为了解决这种问题, 引入事务的概念
ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
原子性: 原子性是指事务是一个不可再分割的工作单元,事务中的操作要么都执行,要么都不执行,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
光有原子性是不够的, 试想下, 多个事务并发操作同一个数据, 其中一个回滚会影响到其他的事务的结果
eg: 原始数据是10, 事务一将数据*2, 事务二将数据-1, 若事务1/2几乎同时执行, 1 先2后,
为了以上的 脏读 / 不可重复读 / 幻读, 引入了 隔离 的概念
隔离性
多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。
最好的隔离是 将事务串行化, 当一个事务操作一个表时, 其他事务只能等待, 等待这个事务完成以后才能操作这个表
但是显然这样做, 会大大降低性能, 只能做妥协, 给隔离级别分级
事务隔离级别 | 说明 |
---|---|
Serializable (串行化) | 可避免脏读、不可重复读、幻读的发生 |
Repeatable read (可重复读) | 可避免脏读、不可重复读的发生 |
Read committed (读已提交) | 可避免脏读的发生 |
Read uncommitted (读未提交) | 最低级别,任何情况都无法保证 |
思考: 隔离性是如何实现的
事务 ACID 特性
事务除了拥有 原子性和隔离性以外, 还需要有以下特征
原子性(Atomicity)
隔离性 (Isolation)
一致性 (Consistency)
一致性: 一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
在事务完成前后,数据都是要在业务意义上是”正确的“,所以也许术语”correctness“更适合这里的意思。但如果这样定义的话,数据库的位置就很尴尬了,因为保证业务是否正确是要业务代码来最终保证的,数据库能做的非常有限。目前数据库里实现的约束检查,比如唯一约束、外键约束、一些enum测检查、一些数据类型/长度/有效数字的检查等等,对于简单的场景还可以使用。对于复杂的业务约束检查,很难或者不可能实现。有一类数据正确性问题正是由于下面隔离性的使用不当而带来的。 真实复杂业务的数据正确性维护一般用正确的业务代码 + 合法性job来定时执行 + 数据库自身的简单合法性防护一起实现。
持久性 (Durability)
在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
扩展
锁机制
参考
上一篇: 【python】选择排序算法
下一篇: 选择排序-java实现