事务是数据库系统中的重要概念,利用事务处理,可以保证一组操作不会中途停止,它们要么完全执行,要么完全不执行。
为什么需要数据库事务?
转账是生活中常见的操作,比如从A账户转账100元到B账号,站在用户角度而言,这是一个逻辑上的单一操作,然而在数据库系统中至少会分成两个步骤来完成。
- 第一步:将A账户的金额减少100元;
- 第二步:将B账户的金额增加100元;
在这个过程中可能会出现以下问题:
- 转账操作的第一步执行成功,A账户上的钱减少了100元,但是第二步执行失败或者未执行便发生系统崩溃,导致B账户并没有相应增加100元。
- 转账操作刚完成就发生系统崩溃,系统重启恢复时丢失了崩溃前的转账记录。
- 同时又另一个用户转账给B账户,由于同时对B账户进行操作,导致B账户金额出现异常。
为了便于解决这些问题,需要引入数据库事务的概念。
什么是数据库事务?
数据库事务是构成单一逻辑工作单元的操作集合,一个典型的数据库事务如下所示:
BEGIN TRANSACTION //事务开始 SQL1 SQL2 COMMIT/ROLLBACK //事务提交或回滚
对于上面的转账例子,可以将转账相关的所有操作包含在一个事务中。
如下所示:
BEGIN TRANSACTION A账户减少100元 B账户增加100元 COMMIT
事务使系统能够更方便的进行故障恢复以及并发控制,从而保证数据库状态的一致性。
事务的四大特性(ACID)
原子性(Atomicity): 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。原子性仅仅意味着它不能被分解,因此事务就是一个最小的执行单元。
假如A转500块钱给B。原子性保证了A的余额将被扣除500,B的余额将增加500。整个过程要么全部成功,要么全部失败。不能出现A的余额扣除成功,B的余额增加失败的情况。
一致性(Consistency): 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。事务开始之前和事务结束以后,数据不会被破坏。
假如A转500块钱给B。不管成功与否,转入前和转入后两个人的余额和是一致的。
隔离性(Isolation): 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
假设A同时向B、C各转500块钱。如果两个操作同时进行,T1读取A的余额为1000,同时T2读取A的余额也为1000,分别扣除500发给B和C,然后两者都将更新A的余额为500。这样就出现问题了,因为A余额应该是0。
持久性(Durability): 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变.就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如,A有1000元,它把500转给了B。每次我们查询A的余额时,我们应该得到最新的值,不能丢失这些细节。
事务并发存在的问题
假如有以下表数据:
id |
name |
balance |
1 |
John |
1000 |
2 |
Lucy |
1000 |
脏读:
现在有两个事务A、B:
- 事务A查询John的余额1000
- 事务B先扣减John的余额,扣了100
- 最后A 读到的是扣减后的余额
执行顺序 |
事务A |
事务B |
1 |
begin |
|
2 |
begin |
|
3 |
更新John余额,减100 |
|
4 |
查询John余额 |
|
5 |
返回John余额为900 |
事务A、B交替执行,事务A被事务B干扰到了,因为事务A读取到事务B未提交的数据,这就是脏读。
不可重复读:
假设现在有两个事务A和B:
- 事务A先查询Lucy的余额,查到结果是1000
- 事务B 对Lucy的账户余额进行扣减,扣去100后,提交事务
- 事务A再去查询Lucy的账户余额发现变成了900
执行顺序 |
事务A |
事务B |
1 |
begin |
|
2 |
查询Lucy余额1000 |
|
3 |
begin |
|
4 |
更新Lucy余额 |
|
5 |
Lucy余额减100 |
|
6 |
commit |
|
7 |
查询Lucy余额900 |
|
8 |
commit |
在事务A范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复读。
幻读
假设现在有两个事务A、B:
- 事务A先查询id大于2的账户记录,得到记录id=2和id=3的两条记录
- 这时候,事务B开启,插入一条id=4的记录,并且提交了
- 事务A再去执行相同的查询,却得到了id=2,3,4的3条记录了。
执行顺序 |
事务A |
事务B |
1 |
begin |
|
2 |
查询表中所有数据 | |
3 |
返回ID为1,2两条记录 |
|
4 |
begin |
|
5 |
插入id=3,name=jim,balance=100 |
|
6 |
commit |
|
7 |
查询表中所有数据 | |
8 |
返回ID为1,2,3两条记录 |
事务A查询一个范围的结果集,另一个并发事务B往这个范围中插入/删除了数据,并静悄悄地提交,然后事务A再次查询相同的范围,两次读取得到的结果集不一样了,这就是幻读。
事务隔离等级
读未提交(Read Uncommitted)
事务的修改,即使没有提交,对其他事务也都是可见的。这种等级下会出现脏读、不可重复读、幻读。
读已提交(READ-COMMITTED)
事务读取已提交的数据,大多数数据库的默认隔离级别。当一个事务在执行过程中,数据被另外一个事务修改,造成本次事务前后读取的信息不一样。这种等级下会出现、不可重复读、幻读。
可重复读(REPEATABLE-READ )
这个级别是MySQL的默认隔离级别,它解决了脏读的问题,同时也保证了同一个事务多次读取同样的记录是一致的,但这个级别还是会出现幻读的情况。
序列化(SERIALIZABLE )
最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》