事务
事务的基本概念
事务(Transaction)
事务(Transaction)是用户定义的一系列数据库操作。这些操作在逻辑上构成一个不可分割的工作单元,其核心特征是:要么全部成功执行,要么全部不执行。
事务是数据库管理系统中保证数据一致性的核心机制,同时也是进行数据库恢复和并发控制的基本单位。
需要明确区分事务和程序:
- 一个事务可以由一条或多条 SQL 语句组成,有时甚至可以包含整个程序的一部分逻辑。
- 一个应用程序通常会包含多个不同的事务。
引入事务的目的
数据库系统引入事务的主要目的是确保数据的完整性和一致性。
- 一致性状态:指数据库中的所有数据都满足预定义的完整性约束(如主键唯一、外键关联、业务规则等)。
- 事务的目标就是保证数据库从一个一致性状态转换到另一个一致性状态。
在多用户、高并发的环境下,数据库的一致性可能受到以下因素的威胁:
- 并发执行(Concurrency):多个事务同时访问和修改数据,可能导致冲突。
- 事务中止(Abort):事务在执行过程中因各种原因(如程序错误、用户取消)需要回滚。
- 系统崩溃(Crash):数据库服务器或操作系统发生故障,导致内存中的修改丢失。
通过事务处理机制,特别是其 ACID 特性,DBMS 能够有效地应对这些挑战,保障数据的正确性。
银行转账示例
考虑一个经典的银行转账场景:从账户 A 向账户 B 转账 200 元。这个操作通常包含以下数据库步骤:
- 读取账户 A 的余额(
balance_A
)。 - 计算新余额:
new_balance_A = balance_A - 200
。 - 更新账户 A 的余额为
new_balance_A
。 - 读取账户 B 的余额(
balance_B
)。 - 计算新余额:
new_balance_B = balance_B + 200
。 - 更新账户 B 的余额为
new_balance_B
。
1
2
3– 示例 SQL(简化)
UPDATE Accounts SET balance = balance - 200 WHERE account_id = 'A';
UPDATE Accounts SET balance = balance + 200 WHERE account_id = 'B';
一致性要求无论操作成功与否,账户 A 和账户 B 的余额之和应保持不变。
如果在执行完第 3 步后、第 6 步完成前发生系统崩溃或网络中断,账户 A 的钱已被扣除,但账户 B 的钱尚未增加,导致总金额减少,破坏了数据一致性。
将这两个 UPDATE
操作包装在一个事务中。如果事务成功完成(COMMIT),则两个账户的修改都将永久生效。如果中途发生任何错误(需要 ROLLBACK),那么对账户 A 的修改也将被撤销,数据库恢复到事务开始前的状态,保证了总金额不变。这就是事务原子性的体现。
定义事务与 SQL 控制语句
在 SQL 标准和实际的 DBMS 中,事务的开始和结束通常通过特定的 SQL 命令来控制。
事务的生命周期
一个事务通常经历:开始 执行一系列操作 结束(提交或回滚)。
- 事务的开始:
- 在很多 DBMS 中,事务是隐式启动的。当用户连接到数据库后,执行第一条需要事务上下文的 SQL 语句(通常是 DML 语句,如
SELECT
,INSERT
,UPDATE
,DELETE
)时,系统会自动为其开启一个新事务。 - 当
AUTOCOMMIT
设置为OFF
时,事务需要显式结束。 - 某些 DDL 语句(如
CREATE TABLE
,ALTER TABLE
)通常会隐式地提交(COMMIT)当前活动事务,并作为单独的事务执行。
- 在很多 DBMS 中,事务是隐式启动的。当用户连接到数据库后,执行第一条需要事务上下文的 SQL 语句(通常是 DML 语句,如
- 事务的结束:
COMMIT
:- 表示事务成功完成。
- 事务中进行的所有数据库修改(增、删、改)将被永久保存到数据库中(持久化)。
- 释放事务所持有的资源(如锁)。
ROLLBACK
:- 表示事务因错误或用户意愿而失败或中止。
- 撤销该事务自开始以来对数据库所做的所有修改,使数据库恢复到事务开始前的状态。
- 释放事务所持有的资源。
保存点(Savepoint)
在较长的事务中,可以设置保存点(Savepoint)。
- 语法:
SAVEPOINT <savepoint_name>;
- 作用:允许事务部分回滚。使用
ROLLBACK TO SAVEPOINT <savepoint_name>;
命令,可以撤销从指定保存点之后到当前时间点的所有操作,但保留保存点之前的操作。事务本身并未结束,可以继续执行。 - 不带保存点参数的
ROLLBACK
会回滚整个事务。
AUTOCOMMIT
许多数据库客户端工具或连接库默认开启 AUTOCOMMIT
模式。在此模式下,每一条 SQL 语句都被视为一个独立的事务,并在执行后自动提交(如果成功)或回滚(如果失败)。
这对于简单的、单条语句的操作很方便,但对于需要多条语句共同保证原子性的业务逻辑(如银行转账),则必须显式地关闭 AUTOCOMMIT
(SET AUTOCOMMIT OFF;
或通过 API 设置),然后使用 COMMIT
或 ROLLBACK
来管理事务边界。
事务设置与隔离级别
除了基本的 COMMIT
和 ROLLBACK
,SQL 还提供了一些 SET TRANSACTION
语句来控制事务的行为特性。
事务访问模式
SET TRANSACTION READ WRITE;
- 允许事务执行读和写操作(默认模式)。
SET TRANSACTION READ ONLY;
- 限制事务只能执行读操作(如
SELECT
),不能执行写操作(INSERT
,UPDATE
,DELETE
)。 - 设置只读模式有时可以帮助 DBMS 进行优化,并能防止意外的数据修改。
- 限制事务只能执行读操作(如
事务隔离级别
隔离性是 ACID 特性之一,用于定义一个事务的执行在多大程度上不受其他并发事务的影响。SQL 标准定义了四种隔离级别,用以在并发性能和数据一致性之间进行权衡。
在高并发环境下,若不对事务进行隔离,可能出现以下问题:
- 脏读:事务 T1 读取了事务 T2 已修改但尚未提交的数据。如果 T2 最终回滚,T1 读取的数据就是无效的「脏」数据。
- 不可重复读:事务 T1 在同一事务内两次读取同一行数据,但得到的结果不同。这是因为在两次读取之间,有另一个事务 T2 修改了该行数据并提交了。
- 幻读:事务 T1 在同一事务内两次执行相同的范围查询,但第二次查询返回了额外的(或丢失了)行。这是因为在两次查询之间,有另一个事务 T2 插入(或删除)了符合查询条件的行并提交了。
设置隔离级别语法:SET TRANSACTION ISOLATION LEVEL <level>;
。其中 <level>
可以是以下四种之一:
READ UNCOMMITTED
(未提交读)- 最低级别。允许事务读取其他事务未提交的修改。
- 可能导致脏读、不可重复读和幻读。
- 性能最高,但数据一致性最差。通常不建议使用。
- 在此级别下,读操作通常不加锁。写操作仍需加排他锁。
READ COMMITTED
(提交读)- 事务只能读取其他事务已经提交的数据。
- 可以避免脏读。
- 但仍可能发生不可重复读和幻读。
- 这是许多主流数据库(如 Oracle, PostgreSQL, SQL Server)的默认隔离级别。
- 读操作通常加共享锁,读取完后立即释放;写操作加排他锁,持有至事务结束。
REPEATABLE READ
(可重复读)- 保证在同一事务内多次读取同一行数据时,结果始终一致。
- 可以避免脏读和不可重复读。
- 但仍可能发生幻读。
- 某些数据库如 MySQL InnoDB 在此级别下通过 Next-Key Locking 技术解决了幻读问题
- 这是 MySQL InnoDB 存储引擎的默认隔离级别。
- 读操作和写操作加的锁(共享锁和排他锁)都会持有到事务结束。
SERIALIZABLE
(可串行化)- 最高级别。强制事务串行执行,即效果等同于事务一个接一个地顺序执行。
- 可以避免所有并发问题(脏读、不可重复读、幻读)。
- 通过更严格的锁定机制(如范围锁或谓词锁)实现。
- 数据一致性最好,但并发性能最差,可能导致大量等待和死锁。
隔离级别 | 允许的并发问题 | 读操作加锁策略(典型实现) | 写操作加锁策略(典型实现) |
---|---|---|---|
READ UNCOMMITTED | 脏读,不可重复读,幻读 | 通常不加锁 | 排他锁,持有至事务结束 |
READ COMMITTED | 不可重复读,幻读 | 共享锁,读取后立即释放 | 排他锁,持有至事务结束 |
REPEATABLE READ | 幻读(部分数据库可能避免) | 共享锁,持有至事务结束 | 排他锁,持有至事务结束 |
SERIALIZABLE | 无 | 范围/谓词共享锁,持有至事务结束 | 范围/谓词排他锁,持有至事务结束 |
无论在哪种隔离级别下,事务对数据的写操作(INSERT
, UPDATE
, DELETE
)通常都需要获取排他锁(Exclusive Lock),并且这个锁会一直持有到事务结束(COMMIT
或 ROLLBACK
)时才释放。
这是保证事务原子性和防止丢失更新(Lost Update)等问题的基础。
事务的 ACID 特性
事务的四个核心特性,通常缩写为 ACID,是保证数据库可靠运行的基石。
- 原子性(Atomicity)
- 事务被视为一个不可分割的最小工作单元。事务中的所有操作要么全部成功提交,要么在失败时全部回滚,数据库状态恢复到事务开始之前。
- DBMS 通过「日志」和「恢复」机制来保证原子性。
- 一致性(Consistency)
- 事务的执行必须使数据库从一个一致性状态转移到另一个一致性状态。这意味着事务执行前后,数据库都必须满足所有预定义的约束(如数据类型、非空、唯一键、外键、业务规则等)。
- 一致性是事务的最终目标。原子性、隔离性和持久性都是为了确保一致性。
- 注意:DBMS 保证事务本身执行的一致性(通过 AID),但事务逻辑的正确性(业务层面的一致性)需要应用程序开发者来保证。
- 隔离性(Isolation)
- 并发执行的事务之间应该相互隔离,一个事务的执行不应被其他并发事务所干扰。即,从单个事务的角度看,它感觉不到其他事务在同时执行。
- DBMS 通过「并发控制」机制(如锁、多版本并发控制 MVCC)来实现隔离性。不同的隔离级别提供了不同程度的隔离保证。
- 持久性(Durability/Permanence)
- 一旦事务成功提交(
COMMIT
),其对数据库的修改就必须是永久性的,即使之后发生系统崩溃或电源故障,这些修改也不应丢失。 - DBMS 通过日志(特别是预写日志 Write-Ahead Logging, WAL)和恢复机制来保证持久性。提交的事务结果会被可靠地写入非易失性存储(如硬盘)。
- 一旦事务成功提交(