У меня есть законное требование, что в нашей коллекции приложений (с использованием SQL Server) не может быть пробела в их нумерации. Таким образом, если это номера счетов, это не будет разрешено: [1, 2, 3, 4, 8, 10]
, потому что оно не является последовательным. Для этого у нас есть столбец InvoiceNumber
на нашей таблице Invoices
. В дополнение к этому у нас есть таблица InvoiceNumbers
, которая содержит текущий номер счета-фактуры для каждой организации (поскольку каждая организация должна иметь свою собственную последовательность). Затем хранимая процедура отвечает за заполнение InvoiceNumber
по адресу Invoices
атомарно; он либо увеличивает текущий счетчик на 1 в таблице InvoiceNumbers
, и заполняет это новое значение в таблице Invoices
, либо откатывает транзакцию в случае ошибки. Это хорошо работает.Создание последовательных номеров счетов в SQL Server без условия гонки
В настоящее время добавлено новое требование: определенные заказы должны иметь один и тот же счет-фактуру и, таким образом, тот же номер счета-фактуры, тогда как ранее каждый заказ выставлялся отдельно. С этой целью мы создаем счет в начале дня и связываем его с текущим FinancialPeriod
(рабочий день, по существу), который будет являться счетом, используемым для каждого заказа. Тем не менее, возможно, что организация не создает никаких заказов типа, которые требуют совместного выставления счетов, и поэтому не имеет ничего, чтобы выставлять счет в течение дня, который «отбрасывает» первоначально созданный счет-фактуру (поскольку на следующий день создается новый) и создает разрыв.
Теперь самым простым решением для меня было лениво заполнить InvoiceNumber
на общем счете, который создается в начале дня. Если в тот день создается заказ, а InvoiceNumber
по-прежнему NULL
, тогда создайте его. Это гарантирует, что InvoiceNumber никогда не будет использоваться (не имеет значения, что запись Invoice
не используется, она не имеет реального смысла).
С этой целью я создал приведенную ниже хранимую процедуру, которая для существующего Invoice
заполняет InvoiceNumber
, но только если она еще NULL
. Я просто не знаю, как блокируется SQL Server, и есть ли вероятность для состояния гонки, когда две транзакции базы данных определяют, что InvoiceNumber
по-прежнему NULL
, и оба увеличивают счетчик и теряют один номер, создавая пробел.
По существу, этот вопрос с длинными ветвями сводится к: может ли две транзакции с одновременной базой данных входить в блок if(@currentNumber is null)
для того же @invoiceID
здесь?
Блокирующий часть, которую вы видите, я получил здесь, но я не уверен, что это относится к моему делу:
CREATE PROCEDURE [dbo].[CreateInvoiceNumber]
@invoiceID int,
@appID int
AS
BEGIN
SET NOCOUNT ON;
if not exists (select 1 from InvoiceNumbers where ApplicationID = @appID) insert into InvoiceNumbers values (@appID, 1)
declare @currentNumber int = null;
select @currentNumber = convert(int, i.InvoiceNumber)
from Invoices i
with (HOLDLOCK, ROWLOCK)
where i.ID = @invoiceID
if(@currentNumber is null)
begin
update InvoiceNumbers set @currentNumber = Value = Value + 1
where ApplicationID = @appID
update Invoices set InvoiceNumber = @currentNumber where ID = @invoiceID
end
select convert(nvarchar, @currentNumber)
END
EDIT
Как уже упоминалось в мой комментарий, эти и другие операции записи являются частью транзакции базы данных, инициированной логикой приложения C#. Просто обычный BeginTransaction
по SqlConnection
с настройками по умолчанию, который, конечно же, откатывается в случае каких-либо исключений.
См. Этот вопрос, и это ответы на проверенное решение проблемы. http://dba.stackexchange.com/questions/36603/handling-concurrent-access-to-a-key-table-without-deadlocks-in-sql-server –
@MaxVernon - исправьте меня, если я ошибаюсь, но что, похоже, больше нацелено на то, что я делаю, но избегаю и восстанавливаюсь из тупиков, что для меня не является приоритетом. В любом случае, трудно отделить то, что мне нужно от этого ответа, так как оно довольно сложное, чем то, что у меня есть до сих пор. – JulianR
Вот почему я не опубликовал это как ответ. –