Я работаю над приложением, которое должно создавать продукты (например, полисы страхования доставки) при получении уведомлений о мгновенных платежах 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?
Спасибо.
Это n: 1. Родительская таблица на самом деле не предназначена для «пакетов», я назвал их так для иллюстрации. На самом деле, эти «пакеты» подобны заказам, которые можно объединить и отправить вместе, имея единый страховой полис для всей посылки.





Я считаю, что вам действительно нужен уровень изоляции Serializable. Проблема в том, что два потока могут пройти HasInsurancePolicyCheck (хотя я понятия не имею, что будет делать InsurancePolicyDB.Insert или почему он вернет 0)
У вас есть много других вариантов для этого. Один из них использует очередь сообщений и самостоятельно обрабатывает эти запросы. Другой - использовать sp_getapplock и заблокировать некоторый ключ, уникальный для этого пакета. Таким образом, вы не блокируете больше строк или таблиц, чем необходимо.
Спасибо! Кто-то другой в компании также рекомендует выделенную очередь.
Было бы безопаснее и чище, если бы 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, которое не позволяет мне помещать поле идентификатора пакета в таблицу страхового полиса. Спасибо!
Спасибо! Я не знаю, почему я не подумал об этой части "и Package.policy_id имеет значение null"!
Я согласен с идеей "очереди сообщений" в ответе aaronjensen. Если вас беспокоит, что несколько параллельных потоков пытаются одновременно обновить одну и ту же строку данных, вам следует вместо этого попросить потоки вставить свои данные в рабочую очередь, которая затем последовательно обрабатывается одним потоком. Это значительно снижает конкуренцию в базе данных, поскольку целевая таблица обновляется только одним потоком вместо «N», а операции с рабочей очередью ограничиваются вставками потоками обмена сообщениями и чтением / обновлением потоком обработки данных.
Что такое пакет: отношения политики? Это 1: 1?