Ich arbeite in einem Computercafe und habe ein System hier (Smartlaunch), das die Spiellizenzen verfolgt. Ich habe ein Programm geschrieben, das mit diesem System verbunden ist (eigentlich mit seiner Backend-MySQL-Datenbank). Das Programm soll auf einem Client-PC ausgeführt werden und (1) die Datenbank abfragen, um eine nicht verwendete Lizenz aus dem verfügbaren Pool auszuwählen, und dann (2) diese Lizenz als vom Client-PC verwendet markieren.Java: Simultane MySQL-Abfragen von mehreren Clients synchronisieren
Das Problem ist, ich habe einen Nebenläufigkeitsfehler. Das Programm soll gleichzeitig auf mehreren Rechnern gestartet werden, und wenn dies geschieht, versuchen einige Maschinen oft dieselbe Lizenz zu erwerben. Ich denke, das liegt daran, dass die Schritte (1) und (2) nicht synchronisiert sind, dh ein Programm bestimmt, dass Lizenz # 5 verfügbar ist und es auswählt, aber bevor es # 5 als eine andere Kopie des Programms auf einem anderen PC markieren kann versucht, dieselbe Lizenz zu bekommen.
Ich habe versucht, dieses Problem zu lösen, indem ich Transaktionen und Tabellen sperren, aber es scheint keinen Unterschied zu machen - mache ich das richtig? Hier folgt den Code in Frage:
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");
}
}
Ja das sieht so aus, als könnte es leicht ein Autocommit "Bug" sein. Wenn Sie die Anweisung "LOCK TABLES" ausführen und sie autokommt, wird die Tabelle für eine Nanosekunde gesperrt und bei der Ausführung der Transaktion freigegeben. –
wurde in der geposteten Einreichung nicht angezeigt, aber die SmartLaunchDB.getConnection() -Methode setzt autocommit auf false. Sie haben Recht, dass die Auswahl für das Update wahrscheinlich ein besserer Sperrmechanismus ist, aber danke dafür. –
Nun, es ist nicht genug, Autocommit auszuschalten. Sie müssen Ihre Transaktion noch klar abgrenzen, damit die Sperre gültig ist, bis das Update abgeschlossen ist. –