【问题标题】:Serializable Transactions vs SELECT FOR UPDATE可序列化事务与 SELECT FOR UPDATE
【发布时间】:2013-06-02 04:46:02
【问题描述】:

我正在阅读不同的事务隔离级别,并遇到了SERIALIZABLE 隔离级别。我也知道 Postgres、Oracle 和 MySQL 等数据库支持SELECT .. FOR UPDATE 语法。

然而,当我想锁定我希望对其执行更新的一行(或一系列行)时,我很困惑如何使用这些。

过去使用 JPA 时,我总是在查询中使用 @Transactional 加上 LockModeType.PESSIMISTIC_WRITE。这意味着在 SQL 中使用READ_COMMITTED 隔离级别和SELECT .. FOR UPDATE

但是现在,在阅读了 SERIALIZABLE 之后,我想知道如果我将 @Transactional(isolation=SERIALIZABLE) 与普通的 SELECT 一起使用(例如 em.findById 来获取一个分离的实体)会有什么不同),后跟 UPDATE(实体的合并)。

行为是否相同?

例如,我有一个银行系统,我希望在两个账户之间转账。我要求在转移过程中不要干预这些帐户。所以,假设我从一个账户中扣除 -100 并将其记入另一个账户。确保这些帐户仅对执行更新的交易可用的最佳方法是什么?

假设我正在操作 JPA 分离实体,因此在更新之前,我必须从数据库中读取它们,例如findById().

  • 使用@Transactional(isolation=READ_COMMITTED)em.findByIdLockModeType.PESSIMISTIC_WRITE(即SELECT .. FOR UPDATE),然后使用em.merge(即UPDATE)?李>
  • 或者使用@Transactional(isolation=SERIALIZABLE)em.findById,然后em.merge(即UPDATE)?

【问题讨论】:

    标签: database jpa


    【解决方案1】:

    SERIALIZABLE 与使用 SELECT FOR UPDATE 之间的主要区别在于,使用 SERIALIZABLE 时,所有内容始终处于锁定状态。与 SELECT FOR UPDATE 一样,您可以选择锁定的内容和时间。

    因此,如果您只想锁定某些数据,即 BankAccount 而不是其他数据,即 Branch、AccountTypes,那么 SELECT FOR UPDATE 可以为您提供更好的控制,因为 SERIALIZABLE 会阻塞您的整个系统,因为每个事务都是从 ACCOUNT_TYPES 表中选择的.另外,有些事务可能只是想查余额,所以不需要锁定 ACCOUNT 表。

    看,

    http://en.wikibooks.org/wiki/Java_Persistence/Locking

    【讨论】:

    • 使用 SERIALIZABLE 时,一切都始终被锁定”作为一般陈述并不正确。这在很大程度上取决于 DBMS 的实现(例如 Postgres 不这样做)
    • 在可序列化的 PostgreSQL 中确实“例如 PostgreSQL 不会那样做” 会在没有锁的情况下执行所有选择语句,但是当在写入语句中发生并发时,最后一个事务会因为授予可序列化完整性而失败
    • @deFreitas 是的,与显式锁定(唯一的问题可能是性能或死锁)相比,以这种“异常重试”方式编写代码更难
    【解决方案2】:

    在我看来 SERIALIZABLE 不能在这个 BUSINESS 交易中工作,因为你需要在选择一个实体后检查一些条件(例如,如果一个帐户有足够的钱)。并发事务可能会在 SERIALIZABLE 级别获得错误的值,因为它持有 SELECT 的共享(读取)锁。

    但是 SELECT ... FOR UPDATE 会正常工作,因为它会持有排他锁直到事务结束,并且会强制其他事务等待。

    【讨论】:

      【解决方案3】:

      select ... for update 用于行锁定。这将导致尝试对已锁定的行(以及每个外键引用的列)执行select ... for update 的其他事务等待等待锁定事务完成,然后再继续。锁定并发更新的资源应该适用于银行余额更新场景,更新时锁定它,其他使用select ... for update修改相同资源的业务逻辑事务将不得不等待并轮流。

      另一方面,Serializable 通过处理并发事务提交有效性来防止lost updates,就好像它们按顺序提交一样,因此验证了冲突的更改。对于银行转账扣款的情况,在 PostgreSQL 中,第一个提交的事务将获胜,而在相同余额上更新的其他提交事务将在提交时失败。处理失败的提交将是客户端代码的责任。

      我个人更喜欢使用Serializable结合幂等自动重试失败的冲突更改,然后检查更新计数以了解它是否成功更新,然后适当处理。这比记住在多个地方使用for lock 更简单,而且锁定可能会产生大量等待事务,从而导致数据库负载增加。

      【讨论】:

        【解决方案4】:

        如果您使用for update,您可以指定要锁定的行。如果您有多个读取操作,您可以锁定属于事务中特定读取操作的行。另一方面,可序列化总是锁定行以防止被并发读取

        【讨论】:

          猜你喜欢
          • 2011-06-28
          • 1970-01-01
          • 2019-04-16
          • 2018-12-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-15
          • 2018-03-14
          相关资源
          最近更新 更多