一、事务的基本概念
所谓事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
事务的开始和结束可以由用户显示控制。如果用户没有显示地定义事务,则由数据库管理系统按默认规定自动划分事务。
事务是恢复和并发控制的基本单位。
二、事务四大特性
事务的ACID特性:
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性(Durability)
- 原子性
事务是数据库的逻辑工作单元,事务中包括的诸操作要么全做,要么全不做。 - 一致性
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。
当数据库中只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入磁盘,这时数据库就处于一种不正确的状态,或者说是不一致的状态。
由此可见一致性和原子性是密切相关的。 - 隔离性
一个事务的执行不能被其他事务干扰。即一个事务的内部操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰。 - 持续性
持续性也称永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久的。
保证事务ACID特性是事务管理的重要任务。事务ACID特性可能遭到破坏的因素有:
- 多个事务并发执行时,不同事务的操作交叉执行。
- 事务在运行过程中被强行停止。
在第一种情况下,数据库管理系统必须保证多个事务的交叉运行不影响这些事务的原子性。
在第二种情况下,数据库管理系统必须保证被强行终止的事务对数据库和其他事务没有任何影响。
这些就是数据库管理系统中恢复机制和并发控制机制的责任。
三、事务四种隔离级别
SQL92标准中事务的隔离性定义了四种隔离级别,并规定了每种隔离级别下事务并发可能导致的问题(脏读、不可重复读、幻读)是否被解决。
一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性越差。
-
读未提交
读未提交是一个事务可以读到其他未提交事务的数据。容易造成脏读。 -
读已提交 (Oracle 和PostgreSQL默认隔离级别)
一个事务要等另一个事物提交后才能读取数据。若有事务对数据进行更新操作时,读操作事务要等待这个更新事务提交后才能读取数据,可以解决脏读的问题,但是会引入不可重复读问题。
简单来讲就是写事务提交之前不允许其他事务的读操作。 -
可重复读(MySQL默认隔离级别)
在开始读取数据(事务开启)时,不再允许修改操作。这样就可以在同一个事务内多次读取同一数据时,能够保证读取到的数据是一致的。 -
串行化
是最高的事务隔离级别,事务串行化顺序执行,可以避免脏读、不可重复读、幻读问题。但是事务隔离级别效率最低,比较耗费数据库性能。
四、不同隔离级别解决的问题
数据库并发下事务的三种现象:脏读、不可重复读、幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不会 | 可能 | 可能 |
可重复读 | 不会 | 不会 | 可能 |
串行化 | 不会 | 不会 | 不会 |
1、脏读
读取其他事务未提交的数据。
当一个事务A正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,此时,另外一个事务B也访问这个数据,读了未提交事务操作的数据,然后就使用了这个脏数据。
步骤 | 事务A | 事务B |
---|---|---|
1 | 开启事务 BEGIN | |
2 | 开启事务BEGIN | |
3 | 查询余额 select balance from t_acount where user_id=101;(假设结果为100) | |
4 | 修改余额 update t_acount set balance=balance+100 where user_id=101; | |
5 | 查询余额select balance from t_acount where user_id=101;(查询结果为200,脏读) | |
6 | 回滚事务 rollback;(余额仍为100) |
2、不可重复读
在同一个事务中多次读取同一个数据,读取的结果不一致。因为另一个事务也访问该数据并且可能修改了该数据,这种现象称为不可重复读。
步骤 | 事务A | 事务B |
---|---|---|
1 | 开启事务 BEGIN | |
2 | 开启事务BEGIN | |
3 | 查询余额 select balance from t_acount where user_id=101;(假设结果为100) | |
4 | 修改余额 update t_acount set balance=balance+100 where user_id=101; | |
5 | 提交事务 commit; | |
6 | 查询余额select balance from t_acount where user_id=101;(查询结果为200,不可重复读) |
事务B中前后两次读取的结果不一致。
3、幻读
幻读针对的是insert 和delete问题。
同一事务中多次读取数据,读取的结果行数不一致。
步骤 | 事务A | 事务B |
---|---|---|
1 | 开启事务begin | 开启事务begin |
2 | 查询数据,假设表中有两条数据id=1和id=2 | |
3 | 插入数据 id=3 | |
4 | 提交事务commit | |
5 | 插入id=3的数据,可能会提示主键冲突,id=3已存在 |
4、不同问题之间的区别
- 脏读 和 不可重复读
脏读读到的是其他事务未提交的数据,不可重复读读到的是其他事务已提交的数据。 - 不可重复读 和 幻读
不可重复读是读取了其他事务更改后的数据,针对update操作;
幻读是读取了其他事务新增的数据,针对insert和delete操作。
※ 不可重复读解决方案:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改该条数据。
※ 幻读解决方案:使用表级锁,锁定整张表,事务A多次读取数据总量后才释放该锁,这个时候才允许其他事务新增数据。