【问题标题】:Lock and Isolation for resource reservation pattern资源预留模式的锁定和隔离
【发布时间】:2019-03-26 04:39:26
【问题描述】:

我需要使用 Spring 和 MariaDB 解决资源预留模式。 问题很简单,我有一张来宾表,用于存储活动的客人姓名,我必须确保活动的客人人数必须小于或等于最大容量。

这是桌子:

create table guest(
    event int,
    name varchar(50)
)
create index event on guest (event);

什么是正确的数据库锁定程序和隔离级别? 请考虑此代码将在多线程容器中运行。 我选择使用“SELECT...FOR UPDATE”锁定表,以将锁定限制在一个事件行中。

// START TRANSACTION
@Transactional 
public void reserve(int event, String name){
    getJdbc().query("SELECT * FROM guest WHERE id=? FOR UPDATE",event);
    Integer count=getJdbc().queryForObject("SELECT COUNT(*) FROM guest WHERE id=?",Integer.class,event);
    if(count>=MAX_CAPACITY)
        throw new ApplicationException("No room left");
    getJdbc().query("INSERT INTO guest VALUES (?,?)",event,name);
}
// COMMIT

我做了一些测试,似乎我需要 READ_COMMITTED 隔离级别,对吗? 这是我发现的:

这是我第一次必须更改隔离级别,对此我感到有些惊讶,您能否确认标准 MariaDB 隔离级别 REPETABLE_READ 因这种模式而失败?

【问题讨论】:

  • Spring 是否添加了STARTCOMMIT?当它throws时它会做ROLLBACK吗?
  • 漂亮的图形和问题的解释。
  • @RickJames,是的,如果事务管理器启用了事务注释(在这里您还可以指定隔离级别和异常的回滚策略)
  • 什么是“事务管理器”的一部分?
  • 它是一个Spring组件,它使用cglib(或其他替代品)来拦截事务方法入口和出口管理db事务docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…

标签: locking mariadb spring-jdbc isolation-level


【解决方案1】:

问题是,在线程 2 中的事务期间,repeatable_read 保证您看到数据库处于事务开始时的状态。因此,当时尚未完成的交易 1 的影响将被隐藏。 因此,您将始终看到相同数量的记录,独立于其他交易同时进行的操作。所以两个事务都会插入一条记录。

READ_COMMITTED 表示根据文档:“每次一致读取,即使在同一个事务中,也会设置并读取自己的新快照”。新快照意味着,提交的并发事务的结果将被包括在内。

【讨论】:

  • 我对默认的 REPETABLE_READ 设置有点惊讶,这意味着大多数锁定模式都失败了,考虑与发票编号相同的情况(从已发出的发票编号中获取 MAX 值)......我真的想不出任何适合 REPETABLE_READ 隔离的并发/锁定模式。
  • 通常你想避免锁,使用 MVCC。在这种情况下,乐观锁定也是一个很好的模式。如果更新的记录少于预期,您知道,这就是并行发生的事情。
【解决方案2】:

解决问题的建议。这涉及保留一个计数器而不是执行COUNT(*)。 (是的,这违反了没有多余信息的原则。)

CREATE TABLE EventCount ( event ..., ct INT ..., PRIMARY KEY(event) ) ENGINE=InnoDB;

START TRANSACTION;
    INSERT ...;
    UPDATE EventCount
        SET ct = ct + 1
        WHERE event = ?;
    ct = SELECT ct FROM EventCount WHERE event = ?;
    if (ct > max)
    {
        ROLLBACK;
        exit;
    }
COMMIT;

(警告:我尚未验证这是否适用于您的情况。)

【讨论】:

  • 我认为我们在这里遇到了同样的问题。如果两个事务以默认的 REPETABLE_READ 隔离开始,它们就像数据库数据的快照一样,它们不会相互干扰。然后他们不会发现计数溢出,因为他们没有计算其他事务的记录,如果最后一个计数有并发,在两个事务结束时我会找到一个大于最大值的计数器。我猜默认的 REPETABLE_READ 根本不适合这种常见的并发问题。
  • @Tobia - 请注意,无论隔离级别如何,我的建议都会(我认为)阻止 EventCount 的排他锁。可以通过延迟一项交易来解决该块。但随后if 会在延迟事务上强制使用ROLLBACK
  • 如果锁在事务内部,问题是“SELECT count”将只计算来自该事务的数据,而不是来自其他已提交事务的数据,因为隔离级别。这与我的问题相同:如果您使用 REPETABLE_READ 进行隔离,则等待线程无法看到先前获得锁的其他线程提交的数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-27
  • 1970-01-01
相关资源
最近更新 更多