提交和回滚如何在数据库中正常工作?

时间:2018-03-19 12:43:29

标签: java database postgresql jdbc transactions

想象一下两个线程访问一个维护用户余额的数据库,最初是10个单位。

主题1:撤回5单位货币

1.a read balance from DB
1.b decrement balance in memory
1.c write decremented balance in DB

线程2:存入3个单位的货币

2.a read balance from DB 
2.b increment balance in memory
2.c write incremented balance in DB

如果步骤是交错的,并且我们以10的余额开始,那么我们最终可能会遇到丢失的更新问题,如下所示:

Real world seq of events:

1.a read balance from DB (10)
1.b decrement balance in memory (10 - 5 = 5)
2.a read balance from DB (10)
1.c write decremented balance in DB (5)
2.b increment balance in memory (10 + 3 = 13)
2.c write incremented balance in DB (13)

这里我们失去了1.c的更新。 setAutoCommit(false)commit如何解决问题?假设代码是:

setAutoCommit(false)
1.a read balance from DB
1.b decrement balance in memory
1.c write decremented balance in DB
commit(), and if error, rollback()

如果在1.c之前更改了数据库,是否会抛出?我找不到任何解释提交/回滚在错误情况下如何工作的示例。

1 个答案:

答案 0 :(得分:2)

有三种可能性可以避免像这样的丢失更新:

  1. 悲观锁定:

    使用SELECT ... FOR UPDATE从数据库中读取余额。

    这将在读取时锁定行。想要在同一行上工作的并发事务将被锁定,直到第一个事务提交为止。你必须使用显式交易;在JDBC中,您将禁用自动提交。

    对于短交易来说,这是一个很好的策略,如果行上的锁定时间没有问题。

  2. 使用事务隔离进行乐观锁定:

    对要执行此类更新的所有事务使用事务隔离级别REPEATABLE READ

    然后第二个事务在更新时会出现序列化错误。这不是应该传播给用户的错误,而是应该重试事务的标志。

    如果您希望尽可能地保持锁定并且愿意重试接收序列化错误的事务,则这是短事务的好方法。你必须使用显式交易;在JDBC中,您将禁用自动提交。

  3. 使用应用程序进行乐观锁定:

    您更新如下:

    UPDATE account SET balance = <new value>
    WHERE id = ... AND balance = <value you originally read>;
    

    然后检查更新是否已修改行(“更新计数”)。如果没有,则余额在此期间已更改,您应该重试该操作。

    注意:这仅检查自我们读取行以来是否已修改balance。如果您希望更新失败,如果任何有关该行的更改,您将扩展WHERE条件。

    如果读取余额和更新之间的时间不短,这是最好的方法,例如如果中间存在用户交互。它不需要使用数据库事务。