在数据库操作中,事务处理是保障数据一致性和完整性的核心机制。无论是电商平台的订单支付、银行系统的转账交易,还是企业的库存管理,都需要通过事务确保一系列 SQL 操作要么全部成功执行,要么全部失败回滚,避免出现数据错乱的情况。
本章将介绍事务处理的概念、控制事务处理的方法,以及回滚(rollback)、提交(commit)、保留点(savepoint)相关知识。
事务(Transaction)是由一组 SQL 语句组成的逻辑执行单元,这组语句要么全部执行成功,要么全部执行失败,不存在 “部分成功” 的中间状态。
事务具有四个核心特性,通常被称为 ACID 特性,特性如下:
原子性(Atomicity):事务是一个不可分割的整体,所有操作要么全部完成,要么全部回滚,不会中断在中间环节。
一致性(Consistency):事务执行前后,数据库的完整性约束不会被破坏。例如转账业务中,转出账户减少的金额与转入账户增加的金额必须相等。
隔离性(Isolation):多个事务并发执行时,一个事务的执行结果不会被其他事务干扰,每个事务都感觉不到其他事务的存在。
持久性(Durability):事务一旦提交(commit),其执行结果将永久保存到数据库中,即使后续数据库发生故障,数据也不会丢失。
在 SQL 中,并非所有数据库引擎都支持事务。例如 MySQL 中的 MyISAM 引擎不支持事务,而 InnoDB 引擎是支持事务的;Oracle、SQL Server 等主流数据库则默认支持事务。因此,在使用事务前,需确保所使用的数据库引擎支持事务。
控制事务处理的核心目的是管理事务的开始、执行、提交和回滚,确保事务符合 ACID 特性。不同数据库对事务控制的语法略有差异,但核心逻辑一致。通常,事务的控制包括以下关键操作:
启动事务:标记事务的开始边界,后续的 SQL 语句将被纳入该事务的执行单元。
执行事务内的SQL语句:如插入(INSERT)、更新(UPDATE)、删除(DELETE)等数据操作语句。
提交事务:确认事务内所有 SQL 语句执行成功,将结果永久保存到数据库。
回滚事务:若事务内某条 SQL 语句执行失败,撤销事务内所有已执行的操作,恢复到事务开始前的状态。
设置保留点:在事务内设置中间标记,允许回滚到指定标记位置,而无需回滚整个事务。
注意:SQL 中的查询语句(SELECT)不会修改数据,因此即使在事务内执行,也不会影响事务的一致性,回滚时无需撤销查询操作。
ROLLBACK 语句用于撤销事务内所有已执行的未提交操作,将数据库状态恢复到事务开始前的状态。它通常在以下场景使用:
事务内某条 SQL 语句执行失败(如违反约束、数据不存在等)。
业务逻辑需要终止事务(如转账过程中发现账户余额不足)。
ROLLBACK 的语法格式如下:
ROLLBACK; -- 回滚整个事务 ROLLBACK TO 保留点名称; -- 回滚到指定保留点(后续讲解保留点时详细说明)
示例:以 MySQL(InnoDB 引擎)为例,假设我们有一个user_account表,存储用户的账户信息,结构如下:
CREATE TABLE user_account ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, balance DECIMAL(10,2) NOT NULL DEFAULT 0.00 );
插入测试数据:
INSERT INTO user_account (username, balance) VALUES ('张三', 1000.00), ('李四', 500.00);
-- 查询数据结果
> select * from user_account
id|username|balance|
--+--------+-------+
1|张三 |1000.00|
2|李四 | 500.00|
2 row(s) fetched.现在模拟转账场景:张三向李四转账300元,但过程中出现错误(如手动触发回滚):
-- 启动事务(MySQL中默认自动提交,需先关闭自动提交或显式启动事务) > START TRANSACTION 0 row(s) modified. -- 1. 张三账户减少300元 > UPDATE user_account SET balance = balance - 300 WHERE username = '张三' 1 row(s) modified. -- 2. 模拟错误:手动触发回滚(实际场景中可能是其他语句执行失败) > ROLLBACK 0 row(s) modified. -- 验证结果:查询账户余额,发现张三和李四的余额均未变化 > SELECT * FROM user_account id|username|balance| --+--------+-------+ 1|张三 |1000.00| 2|李四 | 500.00| 2 row(s) fetched.
执行结果:张三的余额仍为 1000.00 元,李四的余额仍为 500.00 元,说明 ROLLBACK 成功撤销了 UPDATE 操作。
COMMIT 语句用于确认事务内所有 SQL 语句执行成功,将操作结果永久保存到数据库中。提交后,事务结束,且无法再通过 ROLLBACK 撤销已提交的操作(除非借助数据库备份恢复)。它是事务完成的标志,通常在以下场景使用:
事务内所有 SQL 语句均执行成功,符合业务逻辑要求。
业务流程完成(如转账成功、订单创建完成)。
语法格式如下:
COMMIT;
示例:继续使用上述user_account表,模拟正常的转账流程并提交事务:
-- 启动事务 > START TRANSACTION 0 row(s) modified. -- 1. 张三账户减少300元 > UPDATE user_account SET balance = balance - 300 WHERE username = '张三' 1 row(s) modified. -- 2. 李四账户增加300元 > UPDATE user_account SET balance = balance + 300 WHERE username = '李四' 1 row(s) modified. -- 3. 提交事务(确认所有操作成功) > COMMIT 0 row(s) modified. -- 验证结果:查询账户余额 > SELECT * FROM user_account id|username|balance| --+--------+-------+ 1|张三 | 700.00| 2|李四 | 800.00| 2 row(s) fetched.
执行结果:张三的余额变为 700.00 元,李四的余额变为 800.00 元,说明事务提交成功,操作结果永久保存。此时再执行 ROLLBACK 已无法撤销操作。
注意:不同数据库的默认提交模式不同。例如 MySQL 默认自动提交(每条 SQL 语句单独作为一个事务自动提交),因此在使用显式事务时,需通过START TRANSACTION启动事务,或通过SET AUTOCOMMIT = 0关闭自动提交;Oracle 默认关闭自动提交,需显式使用 COMMIT 提交事务。
保留点是事务内设置的中间标记,用于将事务分割为多个较小的执行单元。当需要回滚时,可通过 ROLLBACK TO 语句回滚到指定保留点,而无需回滚整个事务。这在复杂事务中非常实用,例如一个事务包含多个独立的业务操作,其中某个操作失败时,只需回滚该操作相关的步骤,而保留其他已成功的操作。
语法格式如下:
-- 设置保留点 SAVEPOINT 保留点名称; -- 回滚到指定保留点 ROLLBACK TO 保留点名称; -- 删除保留点(可选,事务提交或回滚后保留点自动失效) RELEASE SAVEPOINT 保留点名称;
示例:假设我们有一个更复杂的业务场景:张三向李四转账 300 元,同时张三还需要向王五转账 200 元(王五账户不存在),此时我们希望只回滚向王五转账的操作,保留向李四转账的操作。
首先插入王五的账户(若不插入,后续 UPDATE 会失败):
INSERT INTO user_account (username, balance) VALUES ('王五', 800.00);执行复杂事务并设置保留点:
-- 启动事务 > START TRANSACTION 0 row(s) modified. -- 1. 张三向李四转账300元 > UPDATE user_account SET balance = balance - 300 WHERE username = '张三' 1 row(s) modified. > UPDATE user_account SET balance = balance + 300 WHERE username = '李四' 1 row(s) modified. -- 设置保留点:after_transfer_lisi(转账李四后) > SAVEPOINT after_transfer_lisi 0 row(s) modified. -- 2. 张三向王五转账200元(假设此处执行失败,如手动触发回滚) > UPDATE user_account SET balance = balance - 200 WHERE username = '张三' 1 row(s) modified. > UPDATE user_account SET balance = balance + 200 WHERE username = '王五' 0 row(s) modified. -- 模拟错误:回滚到after_transfer_lisi保留点,撤销向王五转账的操作 > ROLLBACK TO after_transfer_lisi 0 row(s) modified. -- 提交事务:保留向李四转账的操作 > COMMIT 0 row(s) modified. -- 验证结果 > SELECT * FROM user_account id|username|balance| --+--------+-------+ 1|张三 | 400.00| 2|李四 |1100.00| 2 row(s) fetched.
执行结果:
张三的余额:700.00 - 300.00 = 400.00(向王五转账的 200 元被回滚,未扣除)。
李四的余额:800.00 + 300.00 = 1100.00(操作保留)。
王五的余额:800.00(未增加 200 元,操作被回滚)。
通过保留点,我们成功实现了 “部分回滚”,避免了因某个局部操作失败而导致整个事务被撤销,提高了事务处理的灵活性。