Предположим, у меня есть две таблицы:
Invoice
-------
iInvoiceID int PK not null
dtCompleted datetime null
InvoiceItem
-----------
iInvoiceItemID int PK not null
iInvoiceID int FK (Invoice.iInvoiceID) not null
dtCompleted datetime null
Каждый InvoiceItem может быть выполнен другим процессом (исполняемым файлом), который выполняется на другой машине. Когда процесс будет завершен, я хочу, чтобы он вызывал хранимую процедуру, чтобы поставить отметку в поле InvoiceItem.dtCompleted, и я хочу, чтобы эта хранимая процедура возвращала флаг, указывающий, завершен ли весь счет. Какой бы процесс ни выполнялся, тот, кто завершил выставление счета, собирается запустить другой процесс, чтобы выполнить некоторую окончательную бизнес-логику над накладной, например, поставьте отметку в dtCompleted и отправьте квитанцию по электронной почте. Очевидно, что я хочу, чтобы этот другой процесс запускался только один раз для данного счета.
Вот моя наивная реализация:
CREATE PROCEDURE dbo.spuCompleteInvoiceItem
@iInvoiceItemID INT
AS
BEGIN
BEGIN TRAN
UPDATE InvoiceItem
SET dtCompleted = GETDATE()
WHERE iInvoiceItemID = @iInvoiceItemID
IF EXISTS(SELECT * FROM InvoiceItem WHERE dtCompleted IS NULL
AND iInvoiceID = (SELECT iInvoiceID FROM InvoiceItem
WHERE iInvoiceItemID=@iInvoiceItemID))
SELECT 'NotComplete' AS OverallInvoice
ELSE
SELECT 'Complete' AS OverallInvoice
COMMIT
END
Достаточно ли этого? Или мне нужно повысить уровень сериализации транзакций и, если да, какой уровень обеспечит наилучший баланс производительности и безопасности?
Упреждающие комментарии:
- Я знаю, что могу достичь той же бизнес-цели, внедрив центральную службу параллелизма на уровне процессов / исполняемых файлов, но я думаю, что это излишне. Мой инстинкт заключается в том, что, если я хорошо создаю свою хранимую процедуру и транзакцию, я могу использовать SQL Server в качестве службы межпроцессного параллелизма для этой простой операции, не сильно влияя на производительность или увеличивая частоту взаимоблокировок (пусть у меня будет торт и есть).
- Я не беспокоюсь об обработке ошибок в этом примере. После этого я добавлю правильный материал TRY / CATCH / ROLLBACK / RAISERROR.
Обновление 1:
По мнению экспертов, мне нужен не только самый ограничительный уровень изоляции транзакции - сериализуемый - но мне также нужно заблокировать все InvoiceItems определенного счета-фактуры, прежде чем я сделаю что-либо еще, чтобы другие параллельные вызовы хранимая процедура будет блокироваться до завершения текущей. В противном случае я могу получить тупики. Вот моя последняя версия реализации:
CREATE PROCEDURE dbo.spuCompleteInvoiceItem
@iInvoiceItemID INT
AS
BEGIN
IF @iInvoiceItemID IS NULL RAISERROR('@iInvoiceItemID cannot be null.', 16, 1)
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
DECLARE @iInvoiceID INT
SELECT @iInvoiceID = iInvoiceID
FROM InvoiceItem
WHERE dtCompleted IS NULL
AND iInvoiceID = (SELECT iInvoiceID FROM InvoiceItem WHERE iInvoiceItemID=@iInvoiceItemID)
IF @iInvoiceID IS NULL
BEGIN
-- Should never happen
SELECT 'AlreadyComplete' AS Result
END
ELSE
BEGIN
UPDATE InvoiceItem SET dtCompleted = GETDATE() WHERE iInvoiceItemID = @iInvoiceItemID
IF EXISTS(SELECT * FROM InvoiceItem WHERE iInvoiceID=@iInvoiceID AND dtCompleted IS NULL)
SELECT 'NotComplete' AS Result
ELSE
SELECT 'Complete' AS Result
END
COMMIT
Спасибо
Джордан Ригер