问题的背景
在业务逻辑处理的操作中,通常需要在多个关联的表中修改数据。如果某个操作失败(例如,由于限制违规),那么重要的不仅仅是撤销最后一次操作,而是撤销所有逻辑中的操作。为此,需要使用事务机制和错误处理。
问题
并非所有数据库系统在多步骤过程中默认支持在错误情况下进行回滚(尤其是在使用 TRY/CATCH 和 EXCEPTION 处理程序时)。事务逻辑的错误编写会导致“断片式”的更改(部分数据已更新,部分未更新),这会影响业务完整性。
解决方案
为了正确处理,需要明确打开和结束事务(BEGIN TRANSACTION,COMMIT/ROLLBACK)并捕获错误。在不同的数据库系统中,语法和功能有所不同。
例如对于 MS SQL Server:
CREATE PROCEDURE update_balances(@from INT, @to INT, @amount DECIMAL(10,2)) AS BEGIN BEGIN TRY BEGIN TRANSACTION; UPDATE accounts SET balance = balance - @amount WHERE id = @from; UPDATE accounts SET balance = balance + @amount WHERE id = @to; COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; INSERT INTO error_log(err_message, err_date) VALUES(ERROR_MESSAGE(), GETDATE()); THROW; -- 抛出错误 END CATCH END;
对于 PostgreSQL:
CREATE OR REPLACE FUNCTION update_balances(from_id INT, to_id INT, amount NUMERIC) RETURNS void AS $$ BEGIN UPDATE accounts SET balance = balance - amount WHERE id = from_id; UPDATE accounts SET balance = balance + amount WHERE id = to_id; -- 异常会自动回滚事务 EXCEPTION WHEN OTHERS THEN INSERT INTO error_log(err_message, err_date) VALUES(SQLERRM, NOW()); RAISE; END; $$ LANGUAGE plpgsql;
关键特点:
存储过程中的错误是否总是会自动回滚事务? 不!例如,在 MS SQL Server 中不保证回滚 - 需要显式的 ROLLBACK。在 PostgreSQL 中,任何错误都将使整个事务变得“损坏”并要求结束。
是否可以在一个过程中“部分”提交? 这是不好的实践。建议仅在业务操作的最后一次执行 commit/rollback。部分平台允许“保存点”,但这用于特定任务。
哪些类型的错误不能通过 SQL 的 TRY/CATCH/EXCEPTION 捕获? 某些系统故障(例如,连接到服务器的中断)不会被处理程序捕获,可能使回滚成为不可能。
存储过程更新了三个表,并在没有捕获错误的情况下完成了 COMMIT。结果,在第二个表出错时,第一个表已经被修改,必须手动从备份中“回滚”数据,耗费了数小时。
优点:
简单且“有效”,直到出现故障。
缺点:
不一致性风险;在出错时无法快速恢复。
引入了明确的错误处理 + 事务回滚,并将所有故障记录在单独的日志中。恢复数据库的完整状态只需几秒钟,故障被分析和最小化。
优点:
即使发生故障也能保证数据的完整性;逻辑透明。
缺点:
对于初学者稍微复杂,需要在代码中保持纪律。