【问题标题】:Java method synchronization and database read/writeJava方法同步和数据库读/写
【发布时间】:2018-08-16 09:39:24
【问题描述】:

我有以下读取和写入数据库的方法。它检查用户数是否小于 MAX_USERS,然后添加一个新用户。

addUser(User user){
    //count number of users in the DB, SELECT count(*) from user_table
    count = ...

    if(count<MAX_USERS){
        //add the new user to the DB, INSERT INTO users_table
    }
}     

这里的问题是,如果上面的代码被多个线程调用,那么它们可能会得到相同的计数值。这将导致 users_table 中的条目超过 MAX_USERS 个。

一种解决方案是同步整个方法,但这会影响性能。有没有更好的处理方法?

【问题讨论】:

  • @Andreas 如果您有答案,请使用原子/锁定事务,随时发布。
  • 这看起来像是重复的dba.stackexchange.com/questions/167273/…
  • 请记住,“影响性能”并不意味着它“以显着方式影响性能”。

标签: java database multithreading postgresql concurrency


【解决方案1】:

所有其他答案(synchronized 方法除外)都受制于竞争条件:

如果两个这样的INSERT 语句同时运行,检查行数的子查询都会发现计数不超过最大值,并且两个INSERT 语句都会插入它们的行,可能会将用户计数推到最大值之外.

使用AFTER INSERT ON user_table 触发器的解决方案也会有同样的问题,因为在提交事务之前,事务的效果对并发事务不可见。

唯一的防弹解决方案是在数据库中使用SERIALIZABLE 事务:

import java.sql.Connection;
import java.sql.SQLException;

Connection conn;

...

conn.setAutoCommit(false);

boolean transaction_done = false;

while (! transaction_done) {
    try {
        conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

        // run the SELECT statement that counts the rows

        if (count < MAX_USERS) {
            //add the new user to the DB, INSERT INTO users_table
        }

        conn.commit();

        transaction_done = true;
    } catch (SQLException e) {
        // only retry the transaction if it is a serialization error
        if (! "40001".equals(e.getSQLState()))
            throw e;
    }
}

仅当将用户插入表的所有事务都使用这段代码(或至少使用可序列化事务)时,这才有效。

【讨论】:

  • 所以(只是作为一个总结),如果只通过Java代码访问表,这将是实现具有同步的Java解决方案的阻力最小的路径(它不像人们那样无效确实如此),但如果可以从问题中的 Java 解决方案外部访问该表(无论可能是什么),则需要可序列化的事务。
【解决方案2】:

如果您想避免 Java 代码中的同步和事务管理魔法(您应该使用 IMO),您必须在数据库中处理它。假设您使用的是 PostgreSQL,最好的方法是触发函数。类似情况请参见:

How to write a constraint concerning a max number of rows in postgresql?

【讨论】:

  • 我还假设您可以控制数据库架构,即您可以创建触发器并执行 DDL。
  • 此解决方案受竞态条件限制;见my answer
  • @LaurenzAlbe 我已经阅读了您的回答。您要做的是在应用程序中实现数据库锁定(乐观),而数据库能够以更有效和更一致的方式执行此操作,但您对这个解决方案中的竞争条件有一点看法,如果您使用序列化隔离级别执行查询。
【解决方案3】:

我认为你可以像这样使用插入 SQL:

INSERT INTO test1(name) 
SELECT 'tony'
 WHERE
   EXISTS (
    (select count(*) from test1) <= MAX_USERS
   );  

当insert返回0时,表示计数大于MAX_USERS,则可以使用局部变量保存结果,下次不需要select数据库 只检查局部变量。

【讨论】:

  • a) PostgreSQL 中没有 dual 表 b) 您的子选择未返回 boolean 值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-28
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多