This type of use case is why I wish more mainstream languages would have stricter concepts of purity and side effects.
Conceptually, a function implementing the body of a database transaction should be idempotent. If a language had a function type that did not mutate global or closed-over variables and only affected the world by calling methods on its parameter, it would be difficult to mess up.
Haskell can do this.
Rust’s Fn(&mut txn) -> Ret is kind of close, but it doesn’t express that the function may not have mutable captures, and it does nothing about interior mutability or calling impure functions.