IsolationLevel.RepeatableRead для предотвращения дублирования

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

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

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

Это правильное использование изоляции транзакции RepeatableRead?

Спасибо.

Что такое пакет: отношения политики? Это 1: 1?

Constantin 24.09.2008 03:43

Это n: 1. Родительская таблица на самом деле не предназначена для «пакетов», я назвал их так для иллюстрации. На самом деле, эти «пакеты» подобны заказам, которые можно объединить и отправить вместе, имея единый страховой полис для всей посылки.

devlord 24.09.2008 05:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
1 574
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

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

Спасибо! Кто-то другой в компании также рекомендует выделенную очередь.

devlord 24.09.2008 10:56
Ответ принят как подходящий

Было бы безопаснее и чище, если бы 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

Это лучше всего работает, когда конфликты редки, что, кажется, ваш случай.

См. Мой комментарий выше о соотношении n: 1, которое не позволяет мне помещать поле идентификатора пакета в таблицу страхового полиса. Спасибо!

devlord 24.09.2008 05:16

Спасибо! Я не знаю, почему я не подумал об этой части "и Package.policy_id имеет значение null"!

devlord 24.09.2008 10:55

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

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