Java: синхронизация запросов MySQL от нескольких клиентов - PullRequest
2 голосов
/ 15 января 2011

Я работаю в игровом интернет-кафе, и у нас здесь есть система (smartlaunch), которая отслеживает игровые лицензии. Я написал программу, которая взаимодействует с этой системой (на самом деле, с ее базой данных MySQL). Программа предназначена для запуска на клиентском ПК и (1) запрашивает базу данных, чтобы выбрать неиспользуемую лицензию из доступного пула, а затем (2) помечает эту лицензию как используемую на клиентском ПК.

Проблема в том, что у меня ошибка параллелизма. Программа предназначена для одновременного запуска на нескольких машинах, и когда это происходит, некоторые машины часто пытаются получить одну и ту же лицензию. Я думаю, что это потому, что шаги (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");
    }
}

Ответы [ 4 ]

3 голосов
/ 16 января 2011

Вы должны сделать две вещи:

  • Обернуть ваш код в транзакцию (чтобы не допустить немедленного автоматического снятия блокировок)
  • Используйте SELECT ... FOR UPDATE и mysql даст вамнеобходимая блокировка (освобождается при фиксации)

SELECT ... FOR UPDATE лучше, чем LOCK TABLE, так как она может обойтись с блокировкой на уровне строк вместо автоматической блокировки всей таблицы

1 голос
/ 15 января 2011

Согласно онлайн-руководству правильный синтаксис блокировки:

LOCK TABLES ...

и у вас есть

LOCK TABLE ...

но у вас нет проверки ошибок. Следовательно, вы, вероятно, не можете получить блокировку, и она молча игнорирует это.

FWIW, я бы поместил ваш код очистки (UNLOCK TABLES, conn.commit() и т. Д.) В блок finally, чтобы вы всегда выполняли очистку в случае исключения.

Похоже, что у вас, возможно, есть утечка дескрипторов подключения к базе данных, и вы никогда не снимаете блокировку, если нет свободной лицензии.

0 голосов
/ 16 января 2011

Как это часто бывает, OP - идиот.Код, который я разместил, действительно работал, но я только что обнаружил дублирующую строку в базе данных - я думаю, кто-то дважды ввел одну и ту же лицензию по ошибке.Это заставило меня поверить, что ошибка параллелизма, которую я исправил (введя блокировки таблиц), все еще не исправлена.

Спасибо за общий совет, я представил лучшую обработку исключений для этого метода.

0 голосов
/ 15 января 2011

Я хотел бы предложить просто выполнить оператор обновления и проверить, сколько строк было обновлено. я напишу это в псевдокоде.

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

Если это удастся, вы можете получить лицензионный ключ / идентификатор, выполнив выбор.

...