四大特性
原子性
表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有###操作执行成功,整个事务才提交。事务中的任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。
一致性
事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据库不会破坏。如从A账户转账100元到B账户,不管操作成功与否,A账户和B账户的存款总额是不变的。
隔离性
在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。准确地说,并非要求做到完全无干扰,数据库规定了多种事务的隔离级别,不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
持久性
一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中。即使在提交事务后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。
数据库并发问题
一个数据库可能拥有多个访问客户端,这些客户端都可用并发的方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。这些问题可用归结为5类。
脏读(dirty read)
A事务读取B事务尚未提交的更改数据,并在这个数据的基础上进行操作。如果恰巧B事务回滚,那么A事务读到的数据根本是不被承认的。来看取款事务和转账事务并发时引发的脏读场景。
时 间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元,把余额改为500元 | |
T5 | 查询账户余额为500元(脏读) | |
T6 | 撤销事务,余额恢复为1000元 | |
T7 | 汇入100元,把余额改为600元 | |
T8 | 提交事务 |
在这个场景中,B希望取款500元,而后又撤销了动作,而A往相同的账户中转账100元,就因为A事务读取了B事务尚未提交的数据,而造成账户白白丢失了500元。在Oracle数据库中,不会发生脏读。
不可重复读(unrepeatable read)
不可重复读是指A事务读取了B事务已经提交的更改数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额不一致。
时 间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元,把余额改为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元(和T4读取的不一致) |
幻想读(phantom read)
A事务读取B事务提交的新增数据,这时A事务将出现幻象读现象。幻象读一般发生在计算统计数据的事务中。
时 间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款数为1000元 | |
T4 | 新增一个事务,存款为100元 | |
T5 | 提交事务 | |
T6 | 再次统计,结果为10100元(幻象读) |
如果新增的数据刚好满足事务的查询条件,那么这个新数据就进入了事务的视野,因此产生了两次统计结果不一致的情况。
幻想读和不可重复读是两个容易混淆的概念,前者是指读到了其他已经提交事务的新增数据,而后者是指读到了已经提交事务的更改数据(更改或删除)。
第一类丢失更新(lost update)
A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出。
时 间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元,把余额该为100元 | |
T6 | 提交事务 | |
T7 | 取出100元,把余额改为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000元(丢失更新) |
A事务在撤销时,“不小心”将B事务的转入金额抹除了。
第二类丢失更新(second lost update)
A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失。
时 间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询余额为1000元 | |
T4 | 查询余额为1000元 | |
T5 | 取出100元,把余额改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元 | |
T8 | 提交事务 | |
T9 | 把余额改为1100元(丢失更新) |
在上面的例子中,由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元。相反,如果转账事务先提交,那么用户账户将会损失100元。
数据库锁机制
数据库b并发会引发很多问题,在一些场合下有些问题是允许的,但在另一些场合下可能是致命的。数据库通过锁机制解决并发访问的问题。虽然不同的数据库在实现细节上存在差别,但原理基本上是一致的。
- 行共享锁定
- 行独占锁定
- 表共享锁定
- 表共享独占锁定
- 表独占锁定
事务隔离级别
尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁的机制。只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据库资源添加合适的锁。
ANSI/ISO SQL92标准定义了4个等级的事务隔离级别,在相同的数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可能导致不同的结果。
- 事务隔离级别分类
隔离级别 | 脏读 | 不可重复读 | 幻象读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ UNCOMMITED | 允许 | 允许 | 允许 | 不允许 | 允许 |
READ COMMITTED | 不允许 | 允许 | 允许 | 不允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 | 不允许 | 不允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
SQL 92推荐使用REPEATABLE READ以保证数据的读一致性,不过用户可以根据应用的需要选择适合的隔离等级。