代码之家  ›  专栏  ›  技术社区  ›  satoshi

如何使两个相同的读写并发事务彼此等待?

  •  3
  • satoshi  · 技术社区  · 8 年前

    我有一个从数据库中读取数据的Spring事务,如果查找的内容不存在,它会创建它。例子:

    @Transactional
    public int getUserIdCreateIfNotExistent(String email) throws Exception {
      try {
        return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email);
      } catch (IncorrectResultSizeDataAccessException e) {
        Thread.sleep(10000);
        return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email);
      }
    }
    

    当此方法被同时调用两次时,它将导致两个不同的用户在数据库中使用相同的电子邮件(对唯一电子邮件没有限制,这只是为了说明目的)。

    我希望第二个事务等待第一个事务,以便只创建一个用户。我已尝试将事务隔离级别更改为 Isolation.SERIALIZABLE 但它所做的只是使提交上次回滚的事务回滚。

    编辑: 在任何人建议使用java锁定解决方案之前 synchronized 或者诸如此类,重要的是要了解此方法是web应用程序的一部分,并在命中特定端点时调用。web应用程序在多台不同的机器上运行,并且是负载平衡的,当端点被同时调用两次时就会出现问题。这意味着 代码可以在两台不同的机器上并行执行,它们与另一台机器上的同一数据库通信 .

    2 回复  |  直到 8 年前
        1
  •  2
  •   satoshi    8 年前

    使用咨询锁解决:

    @Transactional
    public int getUserIdCreateIfNotExistent(String email) throws Exception {
      try {
        jdbcTemplate.queryForRowSet("SELECT pg_advisory_xact_lock(hashtext('users'), hashtext(?))", email);
        return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email);
      } catch (IncorrectResultSizeDataAccessException e) {
        Thread.sleep(10000);
        return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email);
      }
    }
    
        2
  •  0
  •   cherouvim    8 年前

    您可以使用“锁”表。它将包含您需要锁定的所有内容,即表名(例如“users”)和行键(例如“email”)。

    如果您需要更快的速度,那么可以使用memcached在服务器之间进行复制。如果您的插入不多,那么db解决方案可能会很好。