事务思维导图
为什么需要事务?
转账是生活中常见的操作,比如从A账户转账100元到B账号。站在用户角度而言,这是一个逻辑上的单一操作,然而在数据库系统中,至少会分成两个步骤来完成:
1.更新A账户的金额,减少100元
2.更新B账户的金额,增加100元。
在这个过程中可能会出现以下问题:
1.转账操作的第一步执行成功,A账户上的钱减少了100元,但是第二步执行失败或者未执行便发生系统崩溃,导致B账户并没有相应增加100元。
2.转账操作刚完成就发生系统崩溃,系统重启恢复时丢失了崩溃前的转账记录。
3.同时又另一个用户转账给B账户,由于同时对B账户进行操作,导致B账户金额出现异常。
为了解决这些问题,需要引入数据库事务的概念。
什么是事务?
数据库事务是***单一逻辑工作单元***的操作集合,是由一系列对数据进行访问与更新的操作所组成的一个程序执行逻辑单元。
BEGIN TRANSACTION //事务开始
SQL1
SQL2
COMMIT/ROLLBACK //事务提交或回滚
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
此外,关于事务的定义有几点需要解释下:
- 数据库事务可以包含一个或多个数据库操作,但这些操作共同构成一个逻辑上的整体。
- 构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部不执行。
- 构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态。
- 以上即使在数据库出现故障以及并发事务存在的情况下依然成立。
事务如何解决上面的问题?
对于上面的转账例子,可以将转账相关的所有操作包含在一个事务中:
BEGIN TRANSACTION
A账户减少100元
B账户增加100元
COMMIT
- 当数据库操作失败或者系统出现崩溃,系统能够以事务为边界进行恢复,不会出现A账户金额减少而B账户未增加的情况。
- 当有多个用户同时操作数据库时,数据库能够以事务为单位进行并发控制,使多个用户对B账户的转账操作相互隔离。
- 事务使系统能够更方便的进行故障恢复以及并发控制,从而保证数据库状态的一致性。
事务的ACID 特性
事务具有4个基本特性,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Duration),简称ACID。
原子性(Atomicity)
事务的原子性是指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中,只允许出现两种状态之一。
全部执行成功
全部执行失败
任何一项操作都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成。
一致性(Consistency)
事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。
比如:如果从A账户转账到B账户,不可能因为A账户扣了钱,而B账户没有加钱。
隔离性(Isolation)
事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。
一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。
隔离性分4个级别,下面会介绍。
持久性(Duration)
事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。
事务的隔离级别
读未提交(READ_UNCOMMITTED)
读未提及,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用。
其隔离级别是最低的,换句话说,如果一个事务正在处理某一数据,并对其进行了更新,但同时尚未完成事务,因此还没有提交事务;而以此同时,允许另一个事务也能够访问该数据。
读已提交(READ_COMMITTED)
读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
可重复读(REPEATABLE_READ)
可重复读就是保证在事务处理过程中,多次读取同一个数据时,该数据的值和事务开始时刻是一致的。因此该事务级别可以避免不可重复读取和脏读,但是有可能出现幻读的数据。
即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决。
顺序读(SERIALIZABLE)
顺序读是最严格的事务隔离级别。它要求所有的事务排队顺序执行,即事务只能一个接一个地处理,不能并发,这样就解决了脏读、不可重复读和幻读的问题了。
事务并发引起的问题
脏读
所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走1000元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了1000块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
示例:
余额应该为1500元才对。请看T5时间点,事务A此时查询的余额为0,这个数据就是脏数据,他是事务B造成的,很明显是事务没有进行隔离造成的。
不可重复读
所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B事务B开启事务–>事务B取走1000元–>提交,数据库里面余额变为0元,此时切换回事务A,事务A再查一次查出账户余额为0元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
示例:
事务A其实除了查询两次以外,其它什么事情都没做,结果钱就从1000编程0了,这就是不可重复读的问题。
幻读
所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如统计总存款,事务A开启事务–>查询总存款金额为1000元,此时切换到事务B,事务B开启事务–>事务B 存入100元,并提交,此时切换回事务A,事务A提交的时候发现总存款变成了 1100,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
时间 | 事务A(统计存款) | 事务B(存款) |
---|---|---|
T1 | 开始事务 | — |
T2 | — | 开始事务 |
T3 | 统计总存款1000元 | — |
T4 | — | 存入100元 |
T5 | — | 提交事务 |
T6 | 提交总存款1100元 | — |
T7 | 提交事务 | — |