Уровень изоляции.Повторяемое чтение для предотвращения дублирования

StackOverflow https://stackoverflow.com/questions/124313

Вопрос

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

Вот базовая схема задействованных таблиц базы данных.

// table "package"
// columns packageID, policyID, other data...
// 
// table "insurancepolicy"
// columns policyID, coverageAmount, other data...

Вот основная схема того, что я хочу сделать:

using (SqlConnection conn = new SqlConnection(...))
{
  sqlTransaction sqlTrans = conn.BeginTransaction(IsolationLevel.RepeatableRead);

  // Calls a stored procedure that checks if the foreign key in the transaction table has a value.
  if (PackageDB.HasInsurancePolicy(packageID, conn))
  { 
    sqlTrans.Commit();
    return false;
  }

  // Insert row in foreign table.
  int policyID = InsurancePolicyDB.Insert(coverageAmount, conn);
  if (policyID <= 0)
  {
    sqlTrans.Rollback();
    return false;
  }

  // Assign foreign key to parent table.  If this fails, roll back everything.
  bool assigned = PackageDB.AssignPolicyID(packageID, policyID, conn);
  if (!assigned)
  {
    sqlTrans.Rollback();
    return false;
  }
}

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

Примечание:Из-за структуры базы данных CRUD каждая хранимая процедура включала либо Чтение (select), Создание (insert), либо Обновление.

Правильно ли это использовать изоляцию транзакции RepeatableRead?

Спасибо.

Это было полезно?

Решение

Было бы безопаснее и чище, если бы insert into Policy просто нажмите на некоторое ограничение таблицы уникальности при попытке вставить дубликат.Повышение уровня изоляции может снизить уровень параллелизма и привести к другим неприятным проблемам, таким как взаимоблокировки.

Другой способ - всегда вставлять строку политики, а затем откатывать ее, если пакет уже был присоединен к Политике:

begin tran (read committed)

/* tentatively insert new Policy */
insert Policy

/* attach Package to Policy if it's still free */
update Package
  set Package.policy_id = @policy_id
  where Package.package_id = @package_id and Package.policy_id is null

if @@rowcount > 0
  commit
else
  rollback

Это лучше всего работает, когда конфликты редки, как, по-видимому, и в вашем случае.

Другие советы

Я полагаю, что вы на самом деле хотите сериализуемый уровень изоляции.Проблема в том, что два потока могут пройти проверку HasInsurancePolicyCheck (хотя я понятия не имею, что будет делать InsurancePolicyDB.Insert или почему он вернет 0)

У вас также есть много других вариантов для этого.Один из них заключается в использовании очереди сообщений и самостоятельной последовательной обработке этих запросов.Другой способ заключается в использовании sp_getapplock - блокировка приложения и зафиксируйте каким-нибудь ключом, уникальным для этого пакета.Таким образом, вы не блокируете больше строк или таблиц, чем необходимо.

Я согласен с идеей "очереди сообщений" в ответе Аарондженсена.Если вас беспокоит, что несколько параллельных потоков пытаются обновить одну и ту же строку данных одновременно, вам следует вместо этого попросить потоки вставить свои данные в рабочую очередь, которая затем последовательно обрабатывается одним потоком.Это значительно уменьшает конкуренцию в базе данных, поскольку целевая таблица обновляется только одним потоком вместо "N", а операции с рабочей очередью ограничены вставками потоками обмена сообщениями и чтением / обновлением потоком обработки данных.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top