NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netstandard,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode。

整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中,代表作有百亿级大数据实时计算项目。

开源地址:https://github.com/NewLifeX/X (求star, 743+)

 

为什么需要事务

事务,通俗来讲,同时干几件事,要么一起成功,要么一起失败

一个比较古老的事务处理案例,(在2002年前后比较常见),就是转账:

  • A给B转账100元,先在A账户减100元,然后在B账号加100元,如果A减成功而B加失败,那么就会数据不一致
  • A给B转账100元,先在B账户加100元,然后在A账号减100元,如果B加成功而A减失败,同样有问题

此时,需要一个事务把两个操作包含起来,伪代码如下:


begin;
    A-=10;
    B+=10;
    commit;
exception
    rollback;
end


这里用白话解释了为什么需要事务,至于事务的原理,绝大部分开发者用不到,感兴趣者可以自行搜索学习。

 

最佳实践

更新订单表信息,每个实体类TEntity都可以 TEntity.Meta.CreateTrans 创建事务。

// 使用using,离开作用域之前,如果没有Commit则自动回滚
using var tran = Order.Meta.CreateTrans();

//todo

// 业务完成,提交事务
tran.Commit();

事务用法

本质上使用了ADO.Net的事务,调用链如下:

UserX.Meta.BeginTrans/UserX.Meta.CreateTrans
=>EntitySession.BeginTrans
=>DAL.BeginTransaction(IsolationLevel.ReadCommitted)
=>IDbSession.BeginTransaction
=>DbConnection.BeginTransaction

因此,得到以下要点:

  • 连接名ConnName相同的多个实体类,共用DAL,只需要选用任意实体类打开一次事务即可
  • 从实体类打开事务跟DAL打开事务,效果一致
  • 不同连接名ConnName的实体类,需要各DAL连接都打开事务
  • 默认的事务保护等级是ReadCommitted,提供了比RepeatableRead更好的并发性能。(金融级别系统请在BeginTransaction中指定使用RepeatableRead/Serializable

最老的用法

从实体类元数据开始

UserX.Meta.BeginTrans();
try
{
    //todo
    UserX.Meta.Commit();
}
catch
{
    UserX.Meta.Rollback();
    throw;
}


UserX和Role共用连接名Membership,以下代码效果与上面一致,不推荐使用

UserX.Meta.BeginTrans();
Role.Meta.BeginTrans();
try
{
    //todo
    UserX.Meta.Commit();
    Role.Meta.Commit();
}
catch
{
    UserX.Meta.Rollback();
    Role.Meta.Rollback();
    throw;
}


基于连接的用法

实体类事务实质上是在连接上开事务,因此同一个连接的多个实体类,不管用哪一个开事务效果都是一样的

var dal = UserX.Meta.Session.Dal;
dal.BeginTransaction();
try
{
    //todo
    dal.Commit();
}
catch
{
    dal.Rollback();
    throw;
}

UserX和Role共用连接名Membership,因此有以下等价:

UserX.Meta.Session.Dal == Role.Meta.Session.Dal == DAL.Create("Membership")


最先进的用法

未提交而离开作用域时执行回滚,还支持多个不同数据库同时打开事务

using (var tran1 = UserX.Meta.CreateTrans())
using (var tran2 = Log.Meta.CreateTrans())
{
    //todo

    tran1.Commit();
    tran2.Commit();
}


UserX连接名Membership,Log连接名Log,因此两个都需要打开事务并提交


在业务代码里面,如果有问题直接抛出异常或者return跳出作用域即可

 

事务对自增的影响

在向带有自增的表插入数据时,如果因事务失败而导致回滚,则已“占用”的自增序数不会归还,导致数据库数据的自增数看起来有“断层”的感觉。