数据库事务概念
数据库事务的4个基本规定(ACID)
数据库对于事务具有严格规定,即必须满足以下4个特性:
· 原子性(Atomic):表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有操作都执行成功,该事务才可以提交;否则已执行的所有操作都必须撤销,数据库回滚到初始状态;
· 一致性(Consistency):事务操作成功后,数据库所处的状态和它的业务规则是一只的,即数据不会遭到破坏;
· 隔离性(Isolation):在并发数据操作时,不同的事务拥有各自的数据空间,他们的操作不会对对方产生干扰;一般来说隔离级别越高,数据的一致性越好,并发性越弱;
· 持久性(Durabiliy):一旦事务提交成功后,事务中的所有数据操作都必须同步到数据库中;即使在提交事务后,数据库马上崩溃,在数据库重启后,也能通过某种机制回复数据;
※在这些事务特性中,一致性是事务的最终目标;
※数据库管理系统一般采用重执行日志来保证原子性、一致性和持久性;重执行日志记录了数据库变化的每一个动作;
※数据库采用数据库锁机制来保证事务的隔离性,类似于Java的线程锁机制,当多个事务视图对相同的数据进行操作时,只有持有事务锁的事务才能操作数据;
数据库锁机制
数据库锁机制用于解决数据库并发产生的问题,保证事务的隔离性;
按锁定的对象不同,可以分为以下2种锁:
· 表锁定:对整张表进行锁定;
· 行锁定:对特定行进行锁定;
从并发事务锁定的关系上,可以分为以下2种锁:
· 共享锁定:防止其他的独占锁定,允许其他共享锁定;
· 独占锁定:防止其他的共享锁定,也防止其他的独占锁定;
由这2类型的锁定组合,不同数据库有不同的锁定实现;
例如 INSERT、UPDATE、DELETE、SELECT FOR UPDATE 等语句都会隐式采用独占的行锁定;
数据并发可能产生的事务问题
在数据库数据并发时,可能会产生以下5种类型的并发错误,包括3种数据数据读写问题(脏读、不可重复读、幻读),2种数据更新问题(第一类丢失问题、第二类丢失问题);
1)脏读(dirty read)
A 事务读取 B事务尚未提交的更改数据,并在该基础上进行操作,如果刚好B事务回滚,A事务读取到的数据是不被承认的;
以下是一个典型的事务A与事务B对同一个账户account并发操作,导致脏读示例:
时间 | 转账事务A | 取账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额 account = 1000; | |
T4 | 取出 500,account = 500 ; | |
T5 | 查询账户余额 accoun = 500(脏读); | |
T6 | 回滚事务,账户余额恢复 account = 1000; | |
T7 | 汇入 100,account = 600; | |
T8 | 提交事务(此时事务A对于account发生了脏读) |
2)不可重复读(unrepeatable read)
A 事务读取了 B事务已经提交的更改数据,照成 A事务前后读取同一个数据不一致的现象;
时间 | 转账事务A | 取账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额 account = 1000; | |
T4 | 查询账户余额 account = 1000 | |
T5 | 取出 200,account = 800; | |
T6 | 提交事务 | |
T7 | 查询到账户余额 account = 800(与T4读取的数据不一致,此时事务 A 还未提交) |
3)幻读(phantom read)
幻读一般发生在计算统计数据的事务中,A事务读取了 B 事务提交的新增行数据,照成 A 事务前后计算统计不一致的现象;
时间 | 转账事务A | 取账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款为 10000; | |
T4 | 新增一个存款账户,存款为 100 | |
T5 | 提交事务 | |
T6 | 再次统计总存款为 100100(幻读) |
4)第一类丢失更新
A 事务回滚时,把已经提交的 B事务的更新数据覆盖了,该现象为第一类丢失更新;
时间 | 转账事务A | 取账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额 account = 1000 | |
T4 | 查询账户余额 account = 1000 | |
T5 | 汇入100,余额 account = 1100 | |
T6 | 提交事务 | |
T7 | 取出100,余额 account = 900 | |
T8 | 回滚事务 | |
T9 | 余额恢复 account = 1000(丢失更新) |
5)第二类丢失更新
A 事务覆盖 B事务已经提交的事务,照成 B 事务所做的操作丢失;
时间 | 转账事务A | 取账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额 account = 1000 | |
T4 | 查询 account = 1000 | |
T5 | 取出 100,余额更改 account = 900 | |
T6 | 提交事务 | |
T7 | 汇入 100,账户余额更改 account = 1100 | |
T8 | 提交事务 | |
T9 | 账户余额最终为 1100(丢失更新) |
数据库隔离级别
尽管数据库已经为用户提供了锁的 DML操作方式,但是直接使用锁管理还是很麻烦,由此数据库为用户提供了自动锁功能;当用户指定会话的事务隔离级别,数据库会自动分析事务中的 SQL语句,然后自动为事务操作的事务资源添加合适的锁,并同时维护这些锁;
ANSI/ISO SQL 92 标准定义了以下由低到高的 4个等级的事务隔离级别;
隔离级别 | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 |
READ UNCOMMITED | 允许 | 允许 | 允许 | 不允许 | 允许 |
READ COMMITED | 不允许 | 允许 | 允许 | 不允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 | 不允许 | 不允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
SQL 92 推荐使用 SERIALIZABLE等级的隔离,以确保数据的读一致性,不过用户可以根据实际需求选择合适和隔离等级;