原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作
的失败都会导致整个事务的失败;
一致性(Consistent):事务结束后系统状态是一致的;
隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难
性的失败。通过日志和同步备份可以在故障发生后重建数据。
补充:关于事务,在面试中被问到的概率是很高的,可以问的问题也是很多的。
首先需要知道的是,只有存在并发数据访问时才需要事务。当多个事务访问同一
数据时,可能会存在 5 类问题,包括 3 类数据读取问题(脏读、不可重复读和幻
读)和 2 类数据更新问题(第 1 类丢失更新和第 2 类丢失更新)。
脏读(Dirty Read):A 事务读取 B 事务尚未提交的数据并在此基础上操作,而 B
事务执行回滚,那么 A 读取到的数据就是脏数据。
时间 转账事务 A
取款事务 B
T1
开始事务
T2
开始事务
T3
查询账户余额为 1000 元第 302 页 共 485 页
T4
取出 500 元余额修改为 500
元
T5
查询账户余额为 500 元(脏读)
T6
撤销事务余额恢复为 1000 元
T7
汇入 100 元把余额修改为 600
元
T8
提交事务
不可重复读(Unrepeatable Read):事务 A 重新读取前面读取过的数据,发现
该数据已经被另一个已提交的事务 B 修改过了。
时间 转账事务 A
取款事务 B
T1
开始事务
T2
开始事务
T3
查询账户余额为 1000 元
T4
查询账户余额为 1000 元
T5
取出 100 元修改余额为 900
元
T6
提交事务
T7
查询账户余额为 900 元(不可重复读)
幻读(Phantom Read):事务 A 重新执行一个查询,返回一系列符合查询条件
的行,发现其中插入了被事务 B 提交的行。第 303 页 共 485 页
时间 统计金额事务 A
转账事务 B
T1
开始事务
T2
开始事务
T3
统计总存款为 10000 元
T4
新增一个存款账户存入 100
元
T5
提交事务
T6
再次统计总存款为 10100 元(幻读)
第 1 类丢失更新:事务 A 撤销时,把已经提交的事务 B 的更新数据覆盖了。
时间 取款事务 A
转账事务 B
T1
开始事务
T2
开始事务
T3
查询账户余额为 1000 元
T4
查询账户余额为 1000 元
T5
汇入 100 元修改余额为 1100
元
T6
提交事务
T7
取出 100 元将余额修改为 900 元
T8
撤销事务
T9
余额恢复为 1000 元(丢失更新)第 304 页 共 485 页
第 2 类丢失更新:事务 A 覆盖事务 B 已经提交的数据,造成事务 B 所做的操作丢
失。
时间 转账事务 A
取款事务 B
T1
开始事务
T2
开始事务
T3
查询账户余额为 1000 元
T4
查询账户余额为 1000 元
T5
取出 100 元将余额修改为 900
元
T6
提交事务
T7
汇入 100 元将余额修改为 1100 元
T8
提交事务
T9
查询账户余额为 1100 元(丢失更新)
数据并发访问所产生的问题,在有些场景下可能是允许的,但是有些场景下可能
就是致命的,数据库通常会通过锁机制来解决数据并发访问问题,按锁定对象不
同可以分为表级锁和行级锁;按并发事务锁定关系可以分为共享锁和独占锁,具
体的内容大家可以自行查阅资料进行了解。
直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定
会话的事务隔离级别,数据库就会通过分析 SQL 语句然后为事务访问的资源加上
合适的锁,此外,数据库还会维护这些锁通过各种手段提高系统的性能,这些对
用户来说都是透明的(就是说你不用理解,事实上我确实也不知道)。ANSI/ISO
SQL 92 标准定义了 4 个等级的事务隔离级别,如下表所示:
隔离级别
脏读
不可重复读 幻读
第一类丢失更新 第二类丢失更新第 305 页 共 485 页
READ
UNCOMMITED
允许
允许
允许
不允许
允许
READ
COMMITTED
不允许 允许
允许
不允许
允许
REPEATABLE
READ
不允许 不允许
允许
不允许
不允许
SERIALIZABLE
不允许 不允许
不允许 不允许
不允许
需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高
并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没
有万能的原则。