2013-10-03 4 views
5

У меня есть законное требование, что в нашей коллекции приложений (с использованием 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 здесь?

Блокирующий часть, которую вы видите, я получил здесь, но я не уверен, что это относится к моему делу:

Pessimistic lock in T-SQL

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 с настройками по умолчанию, который, конечно же, откатывается в случае каких-либо исключений.

+0

См. Этот вопрос, и это ответы на проверенное решение проблемы. http://dba.stackexchange.com/questions/36603/handling-concurrent-access-to-a-key-table-without-deadlocks-in-sql-server –

+0

@MaxVernon - исправьте меня, если я ошибаюсь, но что, похоже, больше нацелено на то, что я делаю, но избегаю и восстанавливаюсь из тупиков, что для меня не является приоритетом. В любом случае, трудно отделить то, что мне нужно от этого ответа, так как оно довольно сложное, чем то, что у меня есть до сих пор. – JulianR

+0

Вот почему я не опубликовал это как ответ. –

ответ

1

Убедитесь, что уровень изоляции базы данных установлен в READ COMMITTED.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED 

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

Также важно отметить, что при обновлении таблицы InvoiceNumbers убедитесь, что она находится в транзакции, и вы хотите, чтобы принципы ACID применялись здесь и все к тому, чтобы быть атомарными (совершая как целую единицу, так и транзакцию).

+0

Спасибо. Я должен был упомянуть, что код приложения C# обертывает эту операцию и любые другие записи, которые происходят в DbTransaction. Так что, если это так, эта хранимая процедура должна быть свободной от гонки? – JulianR

Смежные вопросы