数据库事务这东西,说起来简单,但真正用好,需要细心处理几个关键点。首先,事务不是自动生效的——你得显式调用 `BeginTransaction()` 并绑定后续命令,否则还是默认自动提交。新手最容易犯的错误是只调了 `BeginTransaction`,却忘了把 `SqlCommand.Transaction` 属性绑定上去。这样一来,你眼里的“事务”其实还是自动提交,该回滚的根本没回滚。
SqlConnection.BeginTransaction() 是事务起点,但必须配对使用 Commit/Rollback
事务不是开启就自动生效的,`BeginTransaction()` 返回一个 `SqlTransaction` 对象,后续所有命令都得显式绑定它,否则仍走默认自动提交模式。常见错误是只调用了 BeginTransaction 却忘了把 `SqlCommand.Transaction` 属性设为该对象。容易踩的坑有几个:
- `SqlCommand` 必须在同一个 `SqlConnection` 上创建,且在调用 `BeginTransaction()` 之后、`Commit()` 或 `Rollback()` 之前设置 `Command.Transaction = transaction`
- 事务期间不能关闭连接,也不能在事务未结束时调用 `Connection.Close()`,否则会隐式 Rollback
- 若中途抛异常,必须手动调用 `transaction.Rollback()`;不写 catch 或漏掉 Rollback,连接可能挂起,资源无法释放
用 using 块 + try/catch 管理事务生命周期最稳妥
手动管理 `Commit()` 和 `Rollback()` 容易遗漏分支。推荐用 `using` 包裹连接和事务,并在 `catch` 中明确 Rollback,这比依赖 finally 更直观,也避免因异常跳过关键步骤。示例关键片段:
using (var conn = new SqlConnection(connStr))
{
conn.Open();
using (var trans = conn.BeginTransaction())
{
try
{
var cmd1 = new SqlCommand("INSERT INTO Orders (...) VALUES (...)", conn, trans);
cmd1.ExecuteNonQuery();
var cmd2 = new SqlCommand("UPDATE Inventory SET Stock = Stock - @qty WHERE Id = @id", conn, trans);
cmd2.Parameters.AddWithValue("@qty", qty);
cmd2.Parameters.AddWithValue("@id", productId);
cmd2.ExecuteNonQuery();
trans.Commit(); // 只有到这里才真正提交
}
catch
{
trans.Rollback(); // 出错立刻回滚
throw; // 重新抛出,不吞异常
}
}
}
事务隔离级别影响并发行为,别默认用 ReadCommitted
`BeginTransaction()` 默认用 `IsolationLevel.ReadCommitted`,适合多数场景,但如果你遇到“幻读”或需要更严格一致性(比如金融类扣款+记账),就得显式指定级别。注意:高隔离级别(如 `Serializable`)会加更重锁,可能引发阻塞甚至死锁。选型参考:
- 需要防止脏读、不可重复读 → `RepeatableRead`(比默认更强,但不防幻读)
- 要求绝对一致、可预测结果(如库存校验+扣减原子执行)→ `Serializable`,但务必配合超时和重试逻辑
- 纯读多写少报表类操作 → `ReadUncommitted`(允许脏读,极少用,慎选)
- 设置方式:`conn.BeginTransaction(IsolationLevel.Serializable)`
跨多个 DbContext(EF Core)时,不能靠 SqlConnection.Transaction 混用
如果你混用原生 ADO.NET 和 EF Core,别试图把 `SqlTransaction` 直接赋给 `DbContext.Database.CurrentTransaction`。EF Core 有自己的事务抽象层,底层可能已封装了连接池复用逻辑。强行桥接容易触发 `InvalidOperationException: The connection does not support MultipleActiveResultSets` 或事务不生效。正确做法:
- 纯 EF Core 场景:用 `context.Database.BeginTransaction()`,再传入 `transaction` 到 `ExecuteSqlRaw()` 等方法
- 混合场景(必须同时用 EF 和 SqlCommand):统一用 `context.Database.GetDbConnection()` 获取底层连接,再在其上开事务,并确保所有命令共用该连接实例
- 避免在事务中调用 `context.Sa veChanges()` 后又执行原生 SQL,除非你确认它们共享同一事务上下文
事务真正的难点不在语法,而在边界判断:哪些操作必须捆进同一个事务?连接是否被连接池回收?异常路径是否每条都覆盖了 Rollback?这些地方一疏忽,数据不一致就悄无声息发生了。
本文转载于:https://www.php.cn/faq/2334239.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。