SQL Server事务处理详解:从入门到精通
今天我们来深入探讨SQL Server中非常重要的话题——事务处理。掌握事务处理,让你的数据操作更加安全可靠!
事务是单个的工作单元。如果某一事务成功,则在该事务中进行的所有数据更改均会提交,成为数据库中的永久组成部分。如果事务遇到错误且必须取消或回滚,则所有数据更改均被清除。
简单理解:事务就像银行转账,要么同时成功(扣款+存款),要么同时失败,不会出现扣了钱但没到账的情况。
每条单独的语句都是一个事务。这是SQL Server的默认模式。
-- 每条语句自动提交INSERT INTO users VALUES ('张三', 25)UPDATE users SET age = 26 WHERE name = '张三'
每个事务均以BEGIN TRANSACTION显式开始,以COMMIT或ROLLBACK显式结束。
BEGIN TRANSACTION UPDATE account SET balance = balance - 100 WHERE id = 1 UPDATE account SET balance = balance + 100 WHERE id = 2COMMIT TRANSACTION
在前一个事务完成时,新事务隐式启动,但每个事务仍以COMMIT或ROLLBACK显式完成
-- 开启隐性事务模式SET IMPLICIT_TRANSACTIONS ON-- 以下操作会自动开始新事务INSERT INTO users VALUES ('李四', 30)UPDATE users SET age = 31 WHERE name = '李四'COMMIT TRANSACTION -- 显式提交-- 下一个操作又会自动开始新事务DELETE FROM users WHERE name = '李四'ROLLBACK TRANSACTION -- 显式回滚
标记一个显式本地事务的起始点,将@@TRANCOUNT加1。
-- 基本语法BEGIN TRAN [SACTION] [transaction_name | @tran_name_variable]-- 示例:嵌套事务BEGIN TRAN T1 UPDATE table1 SET col1 = 'value1' -- 嵌套事务M2 BEGIN TRAN M2 WITH MARK '标记说明' UPDATE table2 SET col2 = 'value2' SELECT * FROM table1 COMMIT TRAN M2 UPDATE table3 SET col3 = 'value3'COMMIT TRAN T1
指定由MS DTC管理的分布式事务的起始。
-- 示例:跨服务器更新BEGIN DISTRIBUTED TRANSACTION UPDATE authors SET au_lname = 'McDonald' WHERE au_id = '409-56-7008' -- 调用远程服务器存储过程 EXECUTE link_Server_T.pubs.dbo.changeauth_lname '409-56-7008', 'McDonald'COMMIT TRAN
指定当产生运行时错误时,是否自动回滚当前事务
-- 设置为OFF(默认):只回滚出错语句SET XACT_ABORT OFFBEGIN TRAN INSERT INTO t2 VALUES (1) -- 成功 INSERT INTO t2 VALUES (2) -- 外键错误,只回滚本条 INSERT INTO t2 VALUES (3) -- 成功执行COMMIT TRAN -- 事务提交,1和3被保存-- 设置为ON:任何错误都回滚整个事务SET XACT_ABORT ONBEGIN TRAN INSERT INTO t2 VALUES (4) -- 成功 INSERT INTO t2 VALUES (5) -- 外键错误,整个事务回滚 INSERT INTO t2 VALUES (6) -- 不会执行COMMIT TRAN -- 所有修改都被撤销
在事务内设置保存点,可以有条件地取消事务的一部分。
BEGIN TRANSACTION -- 设置保存点A SAVE TRANSACTION A INSERT INTO demo VALUES('BB', 'B term') -- 回滚到保存点A(只撤销上面的插入) ROLLBACK TRANSACTION A -- 创建新表(不受回滚影响) CREATE TABLE demo2 (name varchar(10), age int) INSERT INTO demo2 VALUES('lis', 1)COMMIT TRANSACTION -- 提交创建表和插入操作
将事务回滚到起点或某个保存点。
-- 回滚整个事务BEGIN TRANSACTION INSERT INTO users VALUES ('王五', 28) UPDATE users SET age = 29 WHERE name = '王五'ROLLBACK TRANSACTION -- 所有修改都被撤销-- 回滚到保存点BEGIN TRANSACTION INSERT INTO users VALUES ('赵六', 22) SAVE TRANSATION SavePoint1 INSERT INTO users VALUES ('钱七', 24) ROLLBACK TRANSACTION SavePoint1 -- 只回滚钱七的插入COMMIT TRANSACTION -- 赵六被提交
标志一个成功的事务结束,使修改成为数据库的永久部分。
BEGIN TRANSACTION A INSERT INTO demo VALUES('BB', 'B term')COMMIT TRANSACTION A -- 提交事务
SQL Server中的嵌套事务将内部事务合并到外部事务中
-- 示例1:嵌套事务回滚BEGIN TRAN t1 INSERT INTO demo2 VALUES('lis', 1) BEGIN TRAN t2 INSERT INTO demo VALUES('BB', 'B term') ROLLBACK TRAN t2 -- 错误!不能回滚内部事务名 INSERT INTO demo2 VALUES('lis', 2)COMMIT TRAN t1-- 示例2:正确的嵌套事务BEGIN TRAN t1 INSERT INTO demo2 VALUES('lis', 1) BEGIN TRAN t2 INSERT INTO demo VALUES('BB', 'B term') COMMIT TRAN t2 -- 内部事务提交 INSERT INTO demo2 VALUES('lis', 2)COMMIT TRAN t1 -- 外部事务提交,所有操作生效
-- 设置锁超时时间为5000毫秒SET LOCK_TIMEOUT 5000-- 设置为0,立即解锁(不等待)SET LOCK_TIMEOUT 0-- 设置为-1(默认),无限等待SET LOCK_TIMEOUT -1
-- 1. READ UNCOMMITTED(脏读)SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED-- 可以读取未提交的数据,性能最好,但数据可能不一致-- 2. READ COMMITTED(默认,避免脏读)SET TRANSACTION ISOLATION LEVEL READ COMMITTED-- 只能读取已提交的数据,但不能保证可重复读-- 3. REPEATABLE READ(可重复读)SET TRANSACTION ISOLATION LEVEL REPEATABLE READ-- 防止其他事务更新已读取的数据,但可能产生幻像行-- 4. SERIALIZABLE(可序列化)SET TRANSACTION ISOLATION LEVEL SERIALIZABLE-- 最严格的级别,防止其他事务插入、更新、删除-- 相当于在所有SELECT语句上设置HOLDLOCK-- 5. SNAPSHOT(快照隔离)SET TRANSACTION ISOLATION LEVEL SNAPSHOT-- 使用行版本控制,读取操作不会阻塞写入操作
BEGIN TRY BEGIN TRANSACTION -- 检查余额 DECLARE @balance DECIMAL(10,2) SELECT @balance = balance FROM accounts WHERE account_id = 1001 AND account_status = 'ACTIVE' IF @balance < 1000 THROW 50000, '余额不足', 1 -- 扣款 UPDATE accounts SET balance = balance - 1000 WHERE account_id = 1001 -- 存款 UPDATE accounts SET balance = balance + 1000 WHERE account_id = 1002 -- 记录交易日志 INSERT INTO transaction_log (from_account, to_account, amount, trans_time) VALUES (1001, 1002, 1000, GETDATE()) COMMIT TRANSACTION PRINT '转账成功'END TRYBEGIN CATCH ROLLBACK TRANSACTION PRINT '转账失败:' + ERROR_MESSAGE()END CATCH
BEGIN TRANSACTION -- 设置保存点 SAVE TRANSACTION OrderCreate -- 创建订单 INSERT INTO orders (order_no, customer_id, order_date, total_amount) VALUES ('ORD2024001', 101, GETDATE(), 299.00) DECLARE @order_id INT = SCOPE_IDENTITY() -- 添加订单明细 INSERT INTO order_details (order_id, product_id, quantity, price) VALUES (@order_id, 1001, 2, 99.50) -- 检查库存 IF NOT EXISTS ( SELECT 1 FROM inventory WHERE product_id = 1001 AND quantity >= 2 ) BEGIN ROLLBACK TRANSACTION OrderCreate PRINT '库存不足,订单创建失败' RETURN END -- 扣减库存 UPDATE inventory SET quantity = quantity - 2 WHERE product_id = 1001COMMIT TRANSACTIONPRINT '订单创建成功'
-- 前提:配置链接服务器和MS DTCBEGIN DISTRIBUTED TRANSACTION -- 本地数据库操作 UPDATE LocalDB.dbo.Products SET Stock = Stock - 5 WHERE ProductID = 101 -- 远程数据库操作 EXEC RemoteDB.warehouse.dbo.sp_UpdateStock @ProductID = 101, @Quantity = -5COMMIT TRANSACTION
保持简短:事务越短,锁定资源的时间越少
避免用户交互:不要在事务中等待用户输入
合理使用隔离级别:根据业务需求选择适当级别
SET XACT_ABORT ON -- 推荐在存储过程中开启SET NOCOUNT ONBEGIN TRY BEGIN TRANSACTION -- 业务逻辑代码 COMMIT TRANSACTIONEND TRYBEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION -- 记录错误日志 INSERT INTO ErrorLog (ErrorMessage, ErrorSeverity, ErrorTime) VALUES (ERROR_MESSAGE(), ERROR_SEVERITY(), GETDATE()) THROW -- 重新抛出错误END CATCH
按相同顺序访问资源
使用较低的隔离级别
考虑使用行版本控制隔离级别
设置合理的锁超时时间
Q1:事务嵌套时,@@TRANCOUNT的值如何变化?
A:每进入一个BEGIN TRANSACTION,@@TRANCOUNT增加1;每执行一个COMMIT,减少1;ROLLBACK会将@@TRANCOUNT置为0。
Q2:如何在事务中查看当前事务层数?
A:使用SELECT @@TRANCOUNT查看当前嵌套层级。
Q3:保存点有什么限制?
A:分布式事务中不支持保存点;回滚到保存点不会释放锁。
事务是保证数据一致性的重要机制,合理使用事务能够:
✅ 保证数据的原子性(Atomicity)
✅ 维护数据的一致性(Consistency)
✅ 实现操作的隔离性(Isolation)
✅ 确保修改的持久性(Durability)
掌握事务处理,让你的数据库操作更加专业可靠!
阅读原文:原文链接