Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/313.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/63.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java:使来自多个客户端的并发MySQL查询同步_Java_Mysql_Concurrency_Locking - Fatal编程技术网

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

Java:使来自多个客户端的并发MySQL查询同步,java,mysql,concurrency,locking,Java,Mysql,Concurrency,Locking,我在一家游戏网吧工作,我们这里有一个系统smartlaunch跟踪游戏许可证。我已经写了一个程序,它实际上与这个系统接口,它的后端MySQL数据库。该程序将在客户端PC上运行,1查询数据库以从可用池中选择未使用的许可证,2将此许可证标记为客户端PC正在使用 问题是,我有一个并发错误。该程序旨在在多台机器上同时启动,当这种情况发生时,一些机器通常会尝试获取相同的许可证。我认为这是因为第1步和第2步不同步,即一个程序确定许可证5可用并选择它,但在它可以将5标记为正在使用之前,另一台电脑上的另一个程序

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

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

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

    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");
    }
}
根据,锁定的正确语法为:

LOCK TABLES ...
你有

LOCK TABLE ...
但是您没有任何错误检查。因此,您可能无法获得锁,而它会默默地忽略这一点

FWIW,我会将清理代码UNLOCK TABLES、conn.commit等放在finally块中,以确保在发生异常时始终正确清理


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

我建议您只执行一个update语句,并检查更新的行数。我会用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。

您必须做两件事:

将代码包装在事务中,以避免自动提交立即释放锁 使用选择。。。对于更新和mysql,将在提交时为您提供所需的锁
选择。。。FOR UPDATE优于LOCK TABLE,因为它可以通过行级锁定来实现,而不是像通常情况那样自动锁定整个表,OP是个白痴。我发布的代码实际上是有效的,但我刚刚在数据库中发现了一个重复的行-我猜有人错误地输入了同一个许可证两次。这让我相信,我通过引入表锁修复的并发错误仍然没有修复


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

这也是我注意到的。虽然本手册在别名示例中使用了锁表。是的,我需要检查该语法是否被允许。但是,除此之外,锁获取看起来是正确的。不过,缺少错误检查仍然是一个问题。是的,这看起来很容易成为自动提交错误。如果执行LOCK TABLES语句,并且该语句自动提交,则该表将被锁定一纳秒,然后在事务提交时释放。该语句未显示在已发布的提交中,但SmartLaunchDB.getConnection方法将自动提交设置为false。你是对的,选择更新可能是一个更好的锁定机制,谢谢。但是,这还不足以关闭自动提交。在更新完成之前,您仍然必须清楚地划分您的事务,以使锁有效。建议您为license ID字段指定一个唯一的索引-这将确保您不能输入同一个索引两次,或者在尝试时会出错。DB是第三方产品的一部分。虽然我同意它应该有一个独特的约束条件,但我并不热衷于将手指伸进我没有代码的软件中。我不知道如果有人尝试输入重复数据,SmartLaunch是否会优雅地处理这样的SQL错误。