Два потока, добавляющие новые строки одновременно - как это предотвратить? - PullRequest
2 голосов
/ 21 июля 2009

В моем приложении у меня есть несколько потоков, которые выполняют некоторую логику. В конце они добавляют новую строку в какую-то таблицу.

Перед добавлением новой строки они проверяют, не существует ли предыдущая запись с такими же данными. Если один найден - они обновляются вместо добавления.

Проблема в том, что какой-то поток A выполняет проверку, видит, что не существует предыдущего объекта с такими же деталями, и непосредственно перед тем, как добавить новую строку, поток B ищет в БД тот же объект. Поток B видит, что такой сущности не существует, поэтому он также добавляет новую строку.

В результате в таблице две строки с одинаковыми данными.

Примечание: ключ таблицы не нарушен, поскольку поток получает следующую последовательность непосредственно перед добавлением строки, а ключ таблицы - это некоторый идентификатор, который не связан с данными.

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

Заранее спасибо за помощь, Рой.

Ответы [ 8 ]

5 голосов
/ 21 июля 2009

Вы говорите о "строках", так что, предположительно, это база данных SQL?

Если так, то почему бы просто не использовать транзакции?

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

4 голосов
/ 21 июля 2009

Вы должны использовать очередь, возможно, блокирующую очередь. Потоки A и B (производители) будут добавлять объекты в очередь, а другой поток C (потребитель) будет опрашивать очередь и удалять самый старый объект из очереди, сохраняя его в БД. Это предотвратит проблему, когда и A, и B одновременно хотят сохранить одинаковые объекты

3 голосов
/ 21 июля 2009

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

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

Но рассмотрим этот аргумент:

Иногда может встречаться любая из следующих последовательностей:

A Вставка Значения V A , B обновляет значения V B .

B вставка V B , A обновляет V A .

Если два потока участвуют в гонке, любой из этих двух исходов V A или V B одинаково действителен. Таким образом, вы не можете отличить второй случай от вставки A V A , а B просто отказывает!

Так что на самом деле может не быть необходимости в случае сбоя и обновления.

2 голосов
/ 21 июля 2009

Я думаю, что это задание для ограничений SQL, а именно "UNIQUE" для набора столбцов, в которых есть данные + соответствующая обработка ошибок.

1 голос
/ 21 июля 2009

Большинство сред баз данных (Hibernate в Java, ActiveRecord и т. Д. В Ruby) имеют форму оптимистической блокировки . Это означает, что вы выполняете каждую операцию в предположении, что она будет работать без конфликтов. В особом случае, когда возникает конфликт, вы проверяете это атомарно в момент выполнения операции с базой данных, генерируете исключение или код возврата ошибки и повторяете операцию в своем клиентском коде после запроса и т. Д.

Обычно это выполняется с использованием номера версии в каждой записи. Когда операция с базой данных выполнена, строка считывается (включая номер версии), код клиента обновляет данные, а затем сохраняет их обратно в базу данных с предложением where, в котором указывается идентификатор первичного ключа И номер версии совпадает. как это было, когда это было прочитано. Если это отличается - это означает, что другой процесс обновил строку, и операция должна быть повторена. Обычно это означает перечитывание записи и повторное выполнение этой операции с новыми данными из другого процесса.

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

Псевдокод будет выглядеть примерно так:

do {
  read row from database
  if no row {
     result_code = insert new row with data
  } else {
     result_code = update row with data
  }
} while result_code != conflict_code

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

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

0 голосов
/ 21 июля 2009

Вам необходимо выполнить проверку существующих строк, а затем обновить / добавить строки в одной транзакции.

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

В псевдо-T-SQL (для Microsoft SQL Server):

BEGIN TRANSACTION
SELECT id FROM MyTable WHERE SomeColumn = @SomeValue WITH UPDLOCK
-- Perform your update here
END TRANSACTION

Блокировка обновления не помешает людям читать из этих записей, но она не позволит людям писать что-либо, что может изменить вывод вашего SELECT

0 голосов
/ 21 июля 2009

Многопоточность всегда сногсшибательна ^^.

Главное, чтобы разграничить критические ресурсы и критические операции.

  • Критический ресурс: ваш стол.
  • Критическая операция: добавление да, но вся процедура

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

0 голосов
/ 21 июля 2009

Вам нужно обернуть вызовы, чтобы проверить и записать строку в критическом разделе или мьютексе.

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

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

Конкретные реализации функциональности критической секции или мьютекса будут зависеть от вашей платформы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...