ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

嵌套事务的秘密:rust-web-app Dbx引用计数事务机制深度剖析

嵌套事务的秘密:rust-web-app Dbx引用计数事务机制深度剖析 嵌套事务的秘密rust-web-app Dbx引用计数事务机制深度剖析【免费下载链接】rust-web-appCode template for a production Web Application using Axum: The AwesomeApp Blueprint for Professional Web Development.项目地址: https://gitcode.com/gh_mirrors/ru/rust-web-app如果你在用 Rust 写后端一定会遇到事务这道坎。rust-web-app 这套生产级 Web 应用模板里Dbx组件用引用计数reference counting解决了一个让人头疼的问题——嵌套事务。当一段业务代码需要在另一个事务里再开一个事务时如何保证只有最外层结束才真正提交或回滚本文带你读懂Dbx的设计看懂它如何用一个小计数器优雅地管好所有嵌套层级。一句话先记住Dbx是 rust-web-app 里负责按需开启数据库事务的存储封装核心靠一个counter计数来实现引用计数式的事务合并。为什么嵌套事务这么难搞先说清楚场景。假设创建用户这个动作需要两步插入一条用户记录再写入它的密码。这两步必须要么都成功、要么都失败。如果只插入了用户却忘了写密码数据库里就会留下一条脏数据。于是我们想把这两步包在一个事务里。但麻烦在于现实代码是分层调用的。你可能在方法 A 里开了事务方法 A 又调用方法 B而方法 B 内部也想开一个事务。这时就会冒出两个问题数据库连接有限不能每层都真的BEGIN一个新事务否则白白占用连接甚至可能触发数据库在事务中不能再开事务的限制。提交时机难判断最里层方法提交得太早外层就控制不了了。Dbx的答案很直接内层请求开事务时不真正新建而是给当前事务的计数器 1内层结束时计数器 -1只有减到 0 才真正COMMIT/ROLLBACK。这就是引用计数思想在事务上的妙用。Dbx 长什么样核心结构一览Dbx的定义非常克制整个结构体只有三个字段却精准覆盖了事务管理的需要pub struct Dbx { db_pool: Db, // 数据库连接池 txn_holder: ArcMutexOptionTxnHolder, // 共享的事务持有者 with_txn: bool, // 是否允许事务模式 }三个字段各司其职字段作用通俗理解db_pool底层连接池工具箱平时查询都从这里拿连接txn_holder用Arc Mutex共享的事务容器共享记事本记录当前事务和引用次数with_txn开关是否启用事务要不要走事务流程的总闸关键在txn_holder里装了一个TxnHolder它才是真正的引用计数主角struct TxnHolder { txn: Transactionstatic, Postgres, // 真正的数据库事务 counter: i32, // 引用计数 }counter初值为1。每当有一层代码借用这个事务就inc()加 1用完就dec()减 1。只要计数没归零事务就还活着归零那一刻才真正提交或回滚。结构定义见 dbx/mod.rs三个核心方法逐个拆解引用计数逻辑Dbx的事务能力由三个异步方法组成。下面用计数器视角带你走一遍。1️⃣ begin_txn开事务前先看看有没有前辈begin_txn 的精髓在先查后建如果with_txn是false当前 Manager 不允许事务直接报错拒绝开始。拿到共享锁后如果已经有一个TxnHolder说明外层已开过事务——此时不新建只把counter加 1。如果还没有才真正调用db_pool.begin()新建事务放入容器计数从 1 起。这一步正是嵌套合并的关键内层的begin_txn不会打开第二个数据库事务只是让计数器涨上去。2️⃣ commit_txn只有最后一位才真正提交commit_txn 的逻辑很干净先dec()把计数减 1。只有当计数减到0时才取出TxnHolder并真正执行txn.commit()。计数还大于 0 时什么都不做——因为还有外层或别的内层在用这个事务不能提前提交。这就完美解决了最里层提交太早的隐患只有最外层那次commit才会触发数据库的真实提交。3️⃣ rollback_txn同样遵循最后归还原则rollback_txn 采用相同的计数思路取出持有者若counter 1说明还有别的引用在用它只是把计数减 1 后放回容器。只有计数为 1即最后一位时才真正执行txn.rollback()并让容器保持为空。三者配合构成了一个完整闭环开事务 计数加提交/回滚 计数减归零才落地。真实案例创建用户时的事务边界光看抽象逻辑还不够我们看一个真实业务——UserBmc::create创建用户。它在 user.rs 中的流程是教科书级的切换为事务型 Managermm.new_with_txn()生成一个with_txn true的新ModelManager。开启事务mm.dbx().begin_txn().await?。插入用户base::create(...)。更新密码Self::update_pwd(...)。提交事务mm.dbx().commit_txn().await?。let mm mm.new_with_txn()?; // 进入事务模式 mm.dbx().begin_txn().await?; // 计数器 - 1 // ... 插入用户 写入密码都复用这个事务... mm.dbx().commit_txn().await?; // 计数器 - 0真正提交注意new_with_txn()的巧妙它克隆连接池但新建一个独立的Dbx所以事务型 Manager 和非事务型 Manager 互不干扰。默认情况下ModelManager是非事务的每条查询各自独立执行只有当业务需要时才显式切换进事务模式。这个按需开启的设计在 model/mod.rs 中定义得很清楚。连接池默认 5 个连接测试时降为 1见 store/mod.rs。理解这一点你才更好懂为什么不能随便多开事务——连接是稀缺资源。出错时怎么保护细粒度的错误类型Dbx把事务的各种非法状态都拆成了独立的错误枚举便于精准定位而不是笼统地报数据库错误。在 dbx/error.rs 中定义了四种语义错误触发场景给你的提示CannotBeginTxnWithTxnFalse在非事务型 Manager 上调用begin_txn你忘了先new_with_txn()CannotCommitTxnWithTxnFalse在非事务型 Manager 上调用commit_txn同上状态不对TxnCantCommitNoOpenTxn没有开启事务就尝试提交漏了begin_txnNoTxn没有事务却试图回滚顺序或状态有误这套设计的价值在于它把逻辑错误和底层 SQL 错误分开了。上层代码能一眼看出是调用姿势错了而不是被一个模糊的Sqlx错误误导。对新手来说这是排错时非常友好的一点。这套设计能教会我们的 3 件事读懂Dbx其实收获的不只是一个工具而是三条通用的工程智慧引用计数不只是内存管理。它同样能优雅地表达共享资源的生命周期——事务、文件句柄、连接都适用。核心心法加一次引用就 1释放就 -1归零才真正清理资源。用按需开启控制默认复杂度。rust-web-app 让ModelManager默认走非事务路径简单、省资源只在确实需要原子性时才new_with_txn()。这让 90% 的简单查询不必背负事务开销也让代码意图一目了然。共享可变状态要配好并发工具。ArcMutexOptionTxnHolder这个组合看起来重但它精确解决了多个克隆出来的 Manager 要共享同一个事务、还要线程安全的问题。Arc负责跨所有者共享Mutex保证同一时刻只有一个方法能改动计数器二者缺一不可。小结Dbx用不到两百行代码把嵌套事务这个容易翻车的场景收拾得井井有条。它靠TxnHolder里的一个counter实现引用计数内层开事务不真正新建外层计数归零才提交或回滚再配合with_txn开关和细粒度错误构成了一个既简单又健壮的事务封装。如果你想在自己的 Rust 项目里借鉴这种模式不妨从这三个文件入手精读核心实现dbx/mod.rs错误定义dbx/error.rs事务型 Managermodel/mod.rs把引用计数管理事务这套思路吃透下次再遇到事务里套事务的场景你就有了一把趁手的钥匙。【免费下载链接】rust-web-appCode template for a production Web Application using Axum: The AwesomeApp Blueprint for Professional Web Development.项目地址: https://gitcode.com/gh_mirrors/ru/rust-web-app创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表