Какие подходы можно использовать, чтобы предотвратить состояние гонки при добавлении новой записи в базу данных SQL с использованием PHP?

Привет! <br/><br/> Как можно безопасно добавить новую запись в базу данных, чтобы избежать повторной вставки в условиях гонки (race condition)? <br/> У меня есть следующий код: <br/><pre><code class="php">$dbh-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  
    $dbh-&gt;beginTransaction();

$result = $dbh-&gt;exec("INSERT INTO `rating` (`ID`, `count`, `post_id`, `user_id`, `sign_ts`, `date`) SELECT NULL, '4', '1', '1', '".$id."', CURRENT_TIMESTAMP FROM DUAL WHERE NOT EXISTS (SELECT * FROM `rating` WHERE `sign_ts`='".$id."' FOR UPDATE)");

        if($result){
            echo "Запись вставлена";
            echo $dbh-&gt;commit();
            } else {
        $dbh-&gt;rollBack();
            }</code></pre> <br/><br/> Если у вас есть предложения или рекомендации, буду признателен за ваш ответ, спасибо.
  • 31 октября 2024 г. 14:43
Ответы на вопрос 2
Чтобы предотвратить состояние гонки (race condition) при добавлении новой записи в базу данных SQL с использованием PHP, можно применить несколько подходов. Приведённый вами код содержит некоторые элементы, которые помогают, но есть и возможности для улучшения. Вот некоторые из рекомендаций:

### 1. Использование уникальных ограничений

Создайте уникальное ограничение на колонну, по которой вы хотите предотвратить дублирование. Например, если `sign_ts` является уникальным для каждой записи (вы сохраняете уникальные отметки времени для каждого пользователя), вы можете создать уникальный индекс на эту колонку:

```sql
ALTER TABLE `rating` ADD UNIQUE (`sign_ts`);
```

После этого, если вы попытаетесь вставить запись с уже существующим `sign_ts`, база данных вернёт ошибку, что предотвратит дублирование.

### 2. Обработка ошибок в вставке

Используйте обработку ошибок в вашем коде при добавлении записи. Например, можно обрабатывать исключения:

```php
try {
    $dbh->beginTransaction();

    $result = $dbh->exec("INSERT INTO `rating` (`ID`, `count`, `post_id`, `user_id`, `sign_ts`, `date`) 
                           VALUES (NULL, '4', '1', '1', '".$id."', CURRENT_TIMESTAMP)");
    
    // Проверяем успешность вставки
    if ($result) {
        $dbh->commit();
        echo "Запись вставлена";
    }
} catch (PDOException $e) {
    $dbh->rollBack();
    // Если возникла ошибка дублирования
    if ($e->getCode() === '23000') {
        echo "Запись уже существует";
    } else {
        echo "Ошибка: " . $e->getMessage();
    }
}
```

### 3. Использование "Исключения" (Locks)

В вашем коде уже имеется оператор `FOR UPDATE`. Но его применение лучше делать в более мощной транзакции. Убедитесь, что вы блокируете необходимые записи для чтения и записи:

```php
$dbh->beginTransaction();

// Блокируем записи на выборку
$stmt = $dbh->prepare("SELECT * FROM `rating` WHERE `sign_ts` = :sign_ts FOR UPDATE");
$stmt->execute(['sign_ts' => $id]);
if ($stmt->rowCount() === 0) {
    // Если запись не найдена, делаем вставку
    $dbh->exec("INSERT INTO `rating` (`ID`, `count`, `post_id`, `user_id`, `sign_ts`, `date`) 
                 VALUES (NULL, '4', '1', '1', '".$id."', CURRENT_TIMESTAMP)");
    $dbh->commit();
    echo "Запись вставлена";
} else {
    $dbh->rollBack();
    echo "Запись уже существует";
}
```

### 4. Оптимистичная блокировка

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

### 5. Уровень изоляции транзакций

Измените уровень изоляции транзакций. Например, используйте `SERIALIZABLE`, чтобы предотвратить появление состояний гонки:

```php
$dbh->exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
```

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

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