一文读懂什么是数据库事务
Posted 学习吧程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文读懂什么是数据库事务相关的知识,希望对你有一定的参考价值。
大部分人知道数据库事务,也知道事务的ACID特性、隔离级别。但是我相信很多人是说不明白的(估计是“死记硬背”的结果)。下面结合实例来梳理一下数据库事务的概念和原理。
1、什么是事务
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
2、为什么需要事务
假设小张自己账户余额A = 1000元,小李账户余额B = 400元,小张需要向小李转账500元
数据库系统大致是这么做:
更新A,A=1000-500
更新B,B=400+500
并且把更新后的结果写入硬盘进行持久化保存
可能出现的异常情况
a、BanlanceA更新后,系统突然出现了故障(比如死机、断电等),导致小张账户少了500而小李账户没有增加500
b、转账的同时(转账还未完成),小李在做其他消费(比如购买一个100元的商品),
对于小李
转账程序 读取到小李余额是400,消费程序读取到小李余额也是400,两者会分别进行如下操作
400+500=900
400-100=300
并且尝试把900和300先后写入数据库。不管是哪个数字后写入,结果都不符合预期
现实状况可能更加复杂,因此需要事务来保证数据库操作的正确性。
3、事务的ACID特性
原子性(Atomicity):事务中的所有操作必须作为一个整体,要么全部成功,要么全部失败。
一致性(Consistency):事务执行的结果必须使数据库从一个一致性状态到另一个一致性状态,对于整个数据的完整性保持稳定。
隔离性(Isolation):当多个用户并发操作数据库时,相互之间不能受到影响。对数据库的影响和他们串行执行时一致。
持久性(Durability):事务一旦提交,它对于数据库的改变是永久的。系统故障不会导致这个改变丢失。
4、事务隔离级别
如果事务在执行时会并发操作同一数据,如果没有采取有效的隔离机制,那么会带来一些问题。这里的“同一数据”很类似于程序设计中的“临界区”,为了避免资源竞争导致的并发问题,需要使用同步机制。因此,事务的隔离级别就是事务并发操作同一数据的同步机制。
先来看看不采取有效隔离机制会带来哪些问题。
丢失更新
撤销一个事务时,把其他事务已经提交的更新覆盖,也称“脏写”
小李账户余额有500元,他打算再把手里的200现金存进去。在他存钱的同时,他女朋友去ATM旁边的奶茶店买了两杯奶茶。就在需要放钱进去的时候,小李突然想到一会儿还需要给同学的小孩包个现金红包,因此取消了本次存款。假设这两件事同时发生、并且数据库对事务没有隔离举措(以下例子都是基于这样的假设)
事务1 |
事务2 |
Read:余额=500 |
|
Read:余额=500 |
|
Write:(500-20) |
|
Commit |
|
Write:(500+200) |
|
Rollback:余额=500 |
可以看到,存款事务回滚后,把消费事务写入的结果覆盖了。这样是不符合预期的。
如果两个事务都读取同一行,并且都进行写入并提交,第一个提交的事务所做的改变就会丢失
还是上面的例子,如果最后小李存入了钱,那么余额会被更新为700。此时消费事务所做的更新就丢失了(即消费20元这件事像是没有发生一样),不符合预期。
脏读
一个事务在处理过程中,读取了另一个事务未提交的事务数据。
小李账户有500,女朋友打算买的衣服是649(已经看好了),还准备买一些水果回家吃,可能是20几元。因为平时使用微信扫码比较方便(无需找零),所以决定把手里的200现金存入银行账户。为了提高效率,小李和女朋友分别去存钱和买衣服。小李存入过程中,他女朋友消费成功。此时ATM突发异常,吐出了放入的200,账户余额也恢复成了存入之前的500
事务1 |
事务2 |
Read:余额=500 |
|
Write:(500+200) |
|
Read:余额=700 |
|
... |
|
Rollback:余额=500 |
可以看到,消费事务读取了存款事务尚未提交的数据,而未提交的数据是不稳定的、可能撤回的。当存款事务回滚时,消费事务读取的数据就是错误的。这样显然是不符合预期的。
不可重复读
指同一个事务在整个事务执行过程中对同一数据进行多次读取,但是结果却不同。与脏读的主要区别在于,不可重复读读取的是事务以及提交的数据,而脏读是读取了事务未提交的数据
事务1 |
事务2 |
Read:余额=500 |
|
Read:余额=500 |
|
Write:(500+200) |
|
Commit |
|
Read:余额=700 |
由于存款事务对账户余额的操作,导致消费事务前后两次读取的数据不一致。
幻读(虚读)
读取某个范围的数据时,因为其他事务对数据集中某些数据进行删除或新增,导致该事务前后两次事务查询的结果不一致。
比如,事务1按照条件(年龄介于20和30之间的人)读取用户表,而此时事务2在进行录入操作
事务1 |
事务2 |
Read(20<age<30)=(22,25,26,27) |
|
Write: insert age=28 |
|
Commit |
|
Read(20<age<30)=(22,25,26,27,28) |
事务1前后读取的数据集不一致,出现幻读
幻读和不可重复读的区别在于,幻读一般针对带查询条件、不确定的一批数据,而不可重复读针对确定的某一行。
正式由于上述问题的存在,因此数据库定义了事务的隔离级别来规避上述问题。但是,完全的隔离性会导致系统并发性能很低、降低了资源利用率。因此在实际场景中,可以按照系统的容忍程度,对隔离性要求降低。
SQL标准定义的事务隔离级别从低到高依次为
Read uncommitted(读未提交)
Read committed(读已提交)
Repeatable read(可重复读)
Serializable(串行化)
各个隔离级别下可能发生的并发异常如下表所示:
可能发生的并发异常 |
|||||
事务隔离级别 |
脏写 |
脏读 |
不可重复读 |
幻读 |
丢失更新 |
读未提交 |
可能 |
可能 |
可能 |
可能 |
|
读已提交 |
可能 |
可能 |
可能 |
||
可重复读 |
可能 |
||||
串行化 |
由此可见,所有隔离级别都不允许脏写的出现。这种对应关系也是理论关系,实际的数据库引擎可能有不同的实现。比如,mysql的Innodb在可重复读的隔离级别下,会使用Next-Key Locks(行锁RecordLock和间隙锁GapLock的结合)来消除幻读。在实际应用场景中,一般使用可重复读这个隔离级别。既能满足并发性要求,也能避免上述异常。
好文章,我在看
以上是关于一文读懂什么是数据库事务的主要内容,如果未能解决你的问题,请参考以下文章