数据库事务(Transaction)是数据库管理系统执行过程中的一个逻辑单位,由一系列操作组成。这些操作要么全部成功,要么全部失败。
事务的主要目的是确保数据的一致性和完整性。
ACID原则
事务处理通常遵循ACID原则,这是四个特性首字母的缩写,分别代表原子性、一致性、隔离性和持久性。
原子性(Atomicity):
事务中的所有操作必须作为一个整体来执行,要么全部完成,要么都不执行。如果事务的一部分失败,则整个事务都会回滚到初始状态。这意味着,从外部来看,事务要么完全发生,要么根本不发生。
一致性(Consistency):
事务开始前和结束后,数据库都必须处于一致的状态。这意味着事务不能违反数据库的约束条件,如外键关系、唯一性约束等。事务应该保证数据从一个一致的状态转换到另一个一致的状态。
隔离性(Isolation):
隔离性是指并发执行的事务之间不会互相影响。每个事务都应该独立于其他事务进行,就好像没有其他事务在同时运行一样。为了实现这一点,数据库提供了不同级别的隔离,比如读未提交、读已提交、可重复读以及序列化。
持久性(Durability):
一旦事务被提交,它对数据库所做的更改就是永久性的,并且不会因为之后的故障而丢失。即使系统在此之后崩溃,事务的结果也不会丢失,这是因为事务的日志记录机制能够保证数据在持久存储中的正确性。
隔离级别
事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。SQL标准定义了四个隔离级别,每个级别都解决了并发事务可能遇到的不同问题。
并发问题
并发问题是在多事务环境中由于事务之间的交互而导致的数据不一致问题。
主要的并发问题包括脏读、不可重复读、幻读等。
数据库管理系统通过提供不同的隔离级别来解决这些问题,从而保证事务的隔离性。
脏读
脏读(Dirty Reads)指的是一个事务读取到了另一个事务尚未提交的数据。如果这个未提交的事务后来被回滚了,那么第一个事务读取的数据就是无效的,这就导致了脏读的发生。
假设有两个事务A和B,事务A正在向表中插入一条记录,但还没有提交。此时,事务B读取了这条记录。
如果事务A最终回滚了,那么事务B实际上读取了一个不存在的记录,这就是脏读。
- 事务A:
INSERT INTO account (id, balance) VALUES (1, 100);
- 事务B:
SELECT * FROM account WHERE id = 1;
- 如果事务A此时回滚,事务B查询结果将是错误的。
不可重复读
不可重复读(Non-Repeatable Reads)指的是在一个事务内多次读取同一数据时,得到的结果不一致。这种情况通常发生在其他事务已经提交了对数据的修改之后。
假设事务A先读取了一条记录,然后在事务B更新了这条记录并提交之后,事务A再次尝试读取这条记录。这时候事务A会发现两次读取的结果不一样,这就是不可重复读。
- 事务A:
SELECT balance FROM account WHERE id = 1;
(假设返回100) - 事务B:
UPDATE account SET balance = 200 WHERE id = 1; COMMIT;
- 事务A:
SELECT balance FROM account WHERE id = 1;
(这次返回200)
更新丢失
更新丢失(Lost Update)指两个事务在并发执行时同时读取到了一个数据项的同一版本,然后各自基于这个版本进行修改,最终一个事务的提交覆盖了另一个事务的更新成果,导致其中一个事务的更新工作丢失。
第一类丢失更新:也称为回滚丢失,发生在两个事务中,一个事务回滚,从而撤销了另一个已提交事务的更新。例如,事务B先对某条记录进行更新并提交,紧接着事务A也对该记录进行了更新但随后回滚,这导致事务B的更新被意外撤销。
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元把余额改为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元把余额改为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000元(丢失更新) |
第二类丢失更新:又称为覆盖丢失或两次更新问题,发生在两个事务都提交了更新,但一个事务的提交覆盖了另一个事务的更新。这种情况下,较晚提交的事务的更新效果会“覆盖”较早提交的事务所做的改动。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元把余额改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元 | |
T8 | 提交事务 | |
T9 | 把余额改为1100元(丢失更新) |
幻读
幻读(Phantom Reads)是指在一个事务内多次执行相同的查询时,返回的结果集包含不同的行数。这通常发生在另一个事务插入或删除了某些行之后。
假设事务A先查询了满足某个条件的所有记录,然后事务B在事务A结束之前插入了一条符合条件的新记录。当事务A再次执行相同的查询时,它会发现多了新的记录,这就是幻读。
- 事务A:
SELECT * FROM account WHERE balance > 50;
(假设返回两条记录) - 事务B:
INSERT INTO account (id, balance) VALUES (2, 60); COMMIT;
- 事务A:
SELECT * FROM account WHERE balance > 50;
(这次返回三条记录)
不可重复读和幻读的区别:
不可重复读关注的是在一个事务内,对同一行数据的多次读取结果不一致。针对的是特定的行,关注的是数据的更新。
幻读关注的是在一个事务内,执行范围查询时,由于其他事务插入或删除了数据,导致两次查询的结果集不同。针对的是一组数据的范围,关注的是数据的插入或删除。
隔离级别
以下是四种标准的事务隔离级别,按照其提供的隔离程度从低到高排列。
读未提交(Read Uncommitted)
这是最宽松的隔离级别,事务中的修改在未提交前就可以被其他事务读取到。这种级别允许“脏读”,即一个事务可能读取到另一个事务尚未提交的数据。因此,这个级别很少在实际应用中使用,因为它几乎不提供任何隔离性保护。
读已提交(Read Committed)
在这个级别下,一个事务只能看到已经提交的事务所做的更改。这样就避免了脏读的问题,但仍然存在不可重复读和幻读的问题。例如,一个事务可能会读取到在两次读取之间被另一个事务插入的新行。
可重复读(Repeatable Read)
在这个级别下,事务在整个过程中可以多次读取同一数据,并且返回相同的结果,即使在这段时间内有其他事务更新了该数据。这是通过锁定机制或快照技术实现的,它能防止不可重复读和脏读,但幻读仍可能发生。例如,在两次查询之间,另一个事务可能会插入新的行。
序列化(Serializable)
这是最严格的隔离级别,它通过强制事务以串行方式执行来确保数据的一致性,就像事务依次执行一样。这意味着事务之间不会有并发冲突,但这也可能导致更多的锁竞争,从而降低了系统的吞吐量。在这个级别上,脏读、不可重复读和幻读都不会发生。