手把手教你撸事务


一、事务是什么

事务就是一组原子性的SQL查询,或者说是一个独立的工作单元,事务内的语句要么全部执行成功,要么全部执行失败
银行应用就是一个很典型的例子。假设数据库里有2张表:支票表和储蓄表。现在用户A需要把支票里面的200转到他的储蓄账号,那么至少需要3个步骤:

  1. 检查支票里面的金额是否高于200
  2. 从支票账号中扣除200
  3. 向储蓄账号增加200
    上述的三个操作必须打包在一个事务中,任何一个操作失败,都应该回滚所有的步骤

单纯的事务概念并不是事务的全部,如果在执行第3个步骤时服务器崩溃了,用户就有可能会损失200
所以事务需要具有ACID特性

二、事务的4大特性

1. 原子性(atomicity)

一个事务必须被视为一个不可分割的工作单元,整个事务中,要么全部执行成功,要么全部失败

2. 一致性(consisten)

事务总是从一个一致性状态转换到另一个一致性状态,在前面的例子中,一致性确保了,即使在第3步操作失败,支票账号也不会丢失的200,因为事务最终并没有提交,所以事务所做的修改并没有保存到数据库中

3. 隔离性(isolation)

通常来说,一个事务所做的修改在最终提交之前,对其他事务是不可见的。

4. 持久性(durability)

一旦事务提交了,则其所做的修改将会永久的被保存在数据库中。就算系统崩溃,数据也不会丢失

三、事务的并发问题

当多个线程开启了事务操作数据库中的数据时,数据库系统就要进行隔离操作,以保证各个线程获取数据的准确性,所以,对于不同的事务,采用不同的隔离级别就会有不同的结果

如果不考虑事务的隔离性,就会发生以下的问题

1. 脏读

例子:用户A向用户B转钱,对应以下SQL

UPDATE ACCOUNT SET MONEY = MONEY + 100 	WHERE NAME = 'B'
UPDATE ACCOUNT SET MONEY = MONEY - 100 	WHERE NAME = 'A'

当执行了第一条SQL时,用户A通知B去查看自己的账户,B发现已经到账,现在不管第二条SQL是否执行,只要事务不提交,所有操作都将回滚,用户B再次查看自己的账户会发现钱其实并没有转入成功

总结:一个事务读取了另一个事务修改但是没有提交的数据

2. 不可重复读

事务T1在读取某一个数据,而事务T2立即修改了这个数据并且提交事务给数据库,当事务T1再次读取数据就会获得不一样的数据,就发生了不可重复读

总结:在同一个事务中,同一个查询,在time1时刻读取某一行,和在time2时刻重新读取某一行的值,发现这一行的数据已经被修改了

3. 幻读

事务T1对数据库的所有行的某个数据项从1修改为2,而另一个事务又在表中插入了一条数据,而且这个数据项的值还是1并且提交,当事务T1去查看刚刚修改的数据,就会发现有一行数据没有被修改,其实这行是事务T2刚刚添加的,所以就和出现了幻觉一样,这就是发生了幻读现象

总结: 在同一个事务中当同一次查询执行时,由于其他事务的插入提交,会导致每次返回的结果集不不一致。

四、事务的隔离级别

4种隔离级别,每一种级别都规定了事务中所做的修改,哪些是事务内和事务间可见的,哪些是不可见的
隔离级别越高,性能越低

1. 读未提交

最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务修改但没有提交的数据。所有的并发问题都会出现

2. 读已提交

一个事务可以读到另一个事务提交的数据,可以解决脏读,但是解决不了不可重复的(Oracle默认的隔离级别)

3. 可重复读

可重复读,保证同一个事务,在多次读取同样的数据的的时候,都会得到相同的结果,可以解决脏读和不可重复读,解决不了幻读(Mysql默认的隔离级别)
INNODB引擎通过多版本并发控制解决了幻读

多版本并发控制
为每行记录添加一个版本号,每当修改数据时,版本号加一。在读取事务开始的时候,系统会给事务一个版本号,事务会去读 版本号 <= 当前版本号的数据,这时就算另一个事务插入了一条数据,并且立马提交,新插入数据的版本号会比读取事务的版本号高,因此读取事务读到的数据也不会变

4. 串行化

最高的隔离级别,它通过强制事务排序,强制事务串行执行,使之不可能发生冲突,从而解决幻读

小结:不可重复读侧重于修改,而幻读侧重于新增和删除。解决不可重复读是只需要锁住满足条件的行,解决幻读是需要锁表

五、死锁

死锁指的是两个或者多个事务在同一资源相互竞争,并请求锁定对方占用的资源,从而导致的恶性循环。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。
举个例子:

START TRANSCATION;
UPDATE StockPrice SET money = 3 WHERE id = 1
UPDATE StockPrice SET money = 4 WHERE id = 2
COMMIT;
============================================
START TRANSCATION;
UPDATE StockPrice SET money = 3 WHERE id = 2
UPDATE StockPrice SET money = 4 WHERE id = 1
COMMIT;

如果凑巧,2个事务都执行了第一条更新语句,同时也锁定了该行数据,接着每个事务都去尝试执行第二条语句,却发现改行已经被对方锁定,然后两个事务都在等对方释放锁,同时又持有了对方的锁,就陷入了死循环中。除非又外界因素介入才能解除死锁。

为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如InnDB存储引擎,越能检测死锁的循环依赖,并立即返回一个错误。还有一种解决方式,就是当查询的时间超过了锁的等待超时时间就会自动释放锁,通常来说这种方式不太好

锁的行为和顺序存储引擎相关的。以同样的顺序执行语句,有的存储引擎会产生死锁,有些则不会。死锁产生的双重原因:有些时因为真正的数据冲突,这种情况很难避免,但有些则完全是由于存储引擎的实现方式导致的

死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免,大多数情况下只需要重新执行因死锁回滚的事务即可

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值