商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > C#怎么实现数据库事务处理 C#如何用Transaction保证多条SQL操作的原子性一致性【数据库】

C#怎么实现数据库事务处理 C#如何用Transaction保证多条SQL操作的原子性一致性【数据库】

  发布于2026-07-17 阅读(0)

扫一扫,手机访问

数据库事务这东西,说起来简单,但真正用好,需要细心处理几个关键点。首先,事务不是自动生效的——你得显式调用 `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删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注