likes
comments
collection
share

数据库事务的隔离性

作者站长头像
站长
· 阅读数 4

数据库中的事务是一种机制,允许连续执行一组SQL 指令,如果在执行这一系列指令的过程中出现任何错误,整个事务将会回滚至最初的状态,以此保证事务要么整体成功,要么整体失败,不会出现部分成功、部分失败的情况。

举例来说,假设有两张银行账户表,分别记录了Alice和Bob各自的账户余额,初始状态下两人都有1000元。

数据库事务的隔离性

现在Alice转账300元给Bob,正常的操作流程应该是执行两条SQL语句:

UPDATE balances SET balance = 700 WHERE account = "Alice";
UPDATE balances SET balance = 1300 WHERE account = "Bob"

在执行第一行 SQL 后,如果系统出现问题导致未能执行第二行 SQL,这样会出现Alice的钱被扣除,而Bob的账户却没有增加相应金额的情况。这种错误发生后修复起来会非常麻烦。为此,数据库中的事务(Transaction)机制就是为了解决这类问题而设计的,它可以确保一个事务内的操作要么全部成功,要么全部失败,绝不会停留在中间状态。

在 SQL 中,可以声明事务的开始和结束,并在必要时回滚到原始状态,这样就能有效避免上述问题的发生。通过使用事务,当涉及到诸如银行转账(如从Alice账户扣款并转入Bob账户)这样的操作时,可以确保资金变动要么全部完成,要么都不发生,以此来维护数据库的一致性和完整性。

BEGIN TRANSACTION;
UPDATE balances SET balance = 700 WHERE account = "Alice";
UPDATE balances SET balance = 1300 WHERE account = "Bob";
COMMIT;

关系型数据库在处理事务时通常要求具备四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),即ACID原则。今天我们主要讨论的是“隔离性”。隔离性确保在系统中有多个事务同时执行时,每个事务之间互不影响。

以下是关于隔离性经常会遇到的几种现象,图表中的 Y 轴代表时间顺序。

脏读(Dirty Read)

如果一个事务还未提交,但其他事务却能读取到它已更新但尚未提交的数据,这种情况称为脏读。例如,事务A在执行过程中两次读取Alice的账户余额,第一次读取时数值为1000,但在事务A未结束前,另一个事务B改变了Alice的账户余额为700,但尚未提交。这时事务A再次读取Alice的余额,读取到了尚未提交的700元,这就是脏读。

数据库事务的隔离性

不可重复读(Non-repeatable reads)

在同一事务内,即使使用相同的查询语句多次读取数据,结果却发生了变化,这种情况称为不可重复读。例如,事务A第一次读取到Alice的账户余额为1000,在事务A还未结束时,事务B修改了Alice的余额为700并提交了事务。随后事务A再次执行相同的查询,这次读取到的余额变为700,这就构成了不可重复读。请注意,脏读也是不可重复读的一种特殊情况。

数据库事务的隔离性

幻读(Phantom reads)

在同一个事务内连续两次执行同样的查询条件,但返回的记录数量却不一致,这种现象称为幻读。举例说明,事务A第一次查询账户余额在900到1000元范围内的账户,结果显示有Alice和Bob两人。而在事务A还未结束的同时,事务B将Alice的余额更新为700元。此时,如果事务A再次基于相同的条件查询,结果中的记录数会从原来的2条减至1条,这就发生了幻读。

数据库事务的隔离性

隔离级别

针对上述提到的三种问题,SQL中通过不同的隔离等级来确定哪种等级的隔离性可以解决相应问题。隔离等级共有四种:

  • 读未提交(Read Uncommitted):在这种隔离等级下,事务可以读取到其他事务未提交的数据,所以在此等级下,上述三种问题都没有得到解决。

  • 读已提交(Read Committed):事务只能读取到其他事务已提交的数据,未提交的数据不会被读取,因此在这个等级中解决了脏读问题。

  • 可重复读(Repeatable Read):在一个事务中,只要查询条件相同,每次读取特定字段的数据都会得到相同的结果,这一等级解决了不可重复读问题。

  • 序列化(Serializable):在多个事务并发执行时,只要事务的执行顺序相同,无论何时执行都会得到相同的结果。例如,先执行事务A再执行事务B,在相同的条件下,每次执行都将产生一样的结果。在这个最高的隔离等级下,除了不可重复读之外,连幻读问题也会被一并解决。

数据库事务的隔离性

顺便提及,MySQL的默认隔离等级是可重复读,但可以通过以下命令调整为序列化:

SET SESSION transaction_isolation='SERIALIZABLE';