Java:使来自多个客户端的并发MySQL查询同步

时间:2011-01-15 19:49:19

标签: java mysql concurrency locking

我在游戏网吧工作,我们这里有一个系统(smartlaunch)跟踪游戏许可证。我编写了一个与该系统接口的程序(实际上,它的后端是MySQL数据库)。该程序旨在在客户端PC上运行,并且(1)查询数据库以从可用池中选择未使用的许可,然后(2)将该许可标记为客户端PC正在使用。

问题是,我有一个并发错误。该程序旨在同时在多台计算机上启动,当发生这种情况时,某些计算机通常会尝试获取相同的许可证。我认为这是因为步骤(1)和(2)不同步,即一个程序确定许可证#5可用并选择它,但在它可以标记#5之前使用另一台PC上的另一个程序副本试图获得相同的许可证。

我试图通过使用事务和表锁定来解决这个问题,但它似乎没有任何区别 - 我这样做了吗?以下是有问题的代码:

    public LicenseKey Acquire() throws SmartLaunchException, SQLException {
    Connection conn = SmartLaunchDB.getConnection();
    int PCID = SmartLaunchDB.getCurrentPCID();

    conn.createStatement().execute("LOCK TABLE `licensekeys` WRITE");

    String sql = "SELECT * FROM `licensekeys` WHERE `InUseByPC` = 0 AND LicenseSetupID = ? ORDER BY `ID` DESC LIMIT 1";
    PreparedStatement statement = conn.prepareStatement(sql);
    statement.setInt(1, this.id);
    ResultSet results = statement.executeQuery();

    if (results.next()) {
        int licenseID = results.getInt("ID");
        sql = "UPDATE `licensekeys` SET `InUseByPC` = ? WHERE `ID` = ?";
        statement = conn.prepareStatement(sql);
        statement.setInt(1, PCID);
        statement.setInt(2, licenseID);
        statement.executeUpdate();
        statement.close();
        conn.commit();
        conn.createStatement().execute("UNLOCK TABLES");
        return new LicenseKey(results.getInt("ID"), this, results.getString("LicenseKey"), results.getInt("LicenseKeyType"));
    } else {
        throw new SmartLaunchException("All licenses of type " + this.name + "are in use");
    }
}

4 个答案:

答案 0 :(得分:3)

你必须做两件事:

  • 将您的代码包装在一个事务中(以避免自动提交立即释放锁)
  • 使用SELECT ... FOR UPDATE,mysql将为您提供所需的锁(在提交时发布)

SELECT ... FOR UPDATE优于LOCK TABLE,因为它可以通过行级锁定来实现,而不是自动锁定整个表

答案 1 :(得分:1)

根据online manual,锁定的正确语法是:

LOCK TABLES ...

你有

LOCK TABLE ...

但您没有任何错误检查。因此,你可能无法获得锁定而且它会默默地忽略它。

FWIW,我将您的清理代码(UNLOCK TABLESconn.commit()等)放在finally块中,以确保在发生异常时始终正确清理。

实际上,您似乎可能会泄漏数据库连接句柄,并且如果没有免费许可证,则永远不会释放锁。

答案 2 :(得分:0)

我建议只做一个更新语句并检查更新的行数。我会用psudo代码写出来。

int uniqueId = SmartLaunchDB.getCurrentPCID();;
int updatedRows = execute('UPDATE `licensekeys` SET `InUseByPC` = uniqueId WHERE `InUseByPC` NOT null LIMIT1')
if (updatedRows == 1)
   SUCCESS
else
   FAIL

如果成功,您可以通过选择获取许可证密钥/ ID。

答案 3 :(得分:0)

通常情况下,OP是个白痴。我发布的代码实际上正在运行,但我刚刚在数据库中发现了一个重复的行 - 我猜有人错误地输入了两次相同的许可证。这让我相信我修复的并发错误(通过引入表锁)仍未修复。

感谢您的一般建议,我已经为此方法引入了更好的异常处理。