sql server: достаточно ли этого вложения в транскрипции для получения уникального номера из базы данных? - PullRequest
1 голос
/ 13 ноября 2010

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

Итак, я подумал, поместите это в транзакцию, как это:

begin transaction
        declare @maxNum int
        select @maxNum = MAX(SequenceNumber) from invoice
            where YEAR = @year
        if @maxNum is null
        begin
            set @maxNum = 0
        end
        set @maxNum = @maxNum + 1
        INSERT INTO [Invoice]
           ([Year]
           ,[SequenceNumber]
           ,[DateCreated])
     VALUES
           (@year
           ,@maxNum
           ,GETUTCDATE()
)

    commit transaction

    return @maxNum

Но мне стало интересно, достаточно ли этого, чтобы положить его в сделку? Моей первой мыслью было: он блокирует этот sp для использования другими людьми, но так ли это? Как SQL Server может знать, что заблокировать на первом шаге?

Будет ли эта конструкция гарантировать мне, что никто другой не выполнит часть select @maxnum только тогда, когда я получу updating the @maxnum значение, и в тот момент получаю то же самое @ maxnum, что и я, так что я попал в беду.

Я надеюсь, вы понимаете, чего я хочу достичь, а также, если вы знаете, выбрал ли я правильное решение.

EDIT: также описывается как «Как однопоточную хранимую процедуру»

Ответы [ 3 ]

5 голосов
/ 13 ноября 2010

Если вы хотите сохранить год и порядковый номер в базе данных и создать из этого номер счета, я бы использовал:

  • столбец InvoiceYear (который может полностьювычисляется как YEAR(InvoiceDate))
  • столбец InvoiceID INT IDENTITY, который можно ежегодно сбрасывать на 1
  • создать вычисляемый столбец InvoiceNumber как:

    ALTER TABLE dbo.InvoiceTable
       ADD InvoiceNumber AS CAST(InvoiceYear AS VARCHAR(4)) + '-' +
               RIGHT('000000' + CAST(InvoiceID AS VARCHAR(6)), 6) PERSISTED
    

Таким образом, вы автоматически получаете номера счетов:

2010-000001
......
2010-001234
......
2010-123456

Конечно, если вам нужно более 6 цифр (= 1 миллион счетов) - просто настройте RIGHT()и CAST() для столбца InvoiceID.

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

Таким образом: вам не нужно беспокоиться о параллелизме, хранимых процессах, транзакциях и тому подобном - SQL Server сделает это за вас - бесплатно!

2 голосов
/ 13 ноября 2010

Нет, этого недостаточно.Общая блокировка, установленная параметром select, не будет препятствовать тому, чтобы кто-либо одновременно считывал это же значение.

Измените это:

select @maxNum = MAX(SequenceNumber) from invoice where YEAR = @year

На это:

select @maxNum = MAX(SequenceNumber) from invoice with (updlock, holdlock) where YEAR = @year

Таким образом, вы заменяете общую блокировку блокировкой обновлений, и две блокировки обновлений не совместимы друг с другом.
holdlock означает, что блокировка должна сохраняться до конца транзакции.Поэтому вам все еще нужен бит транзакции.

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

1 голос
/ 17 ноября 2010

Как оказалось, я не хотел блокировать таблицу, я просто хотел выполнять хранимую процедуру по одному.В коде C # я бы наложил блокировку на другой объект, и это то, что здесь обсуждалось http://www.sqlservercentral.com/Forums/Topic357663-8-1.aspx

Так вот, что я использовал

declare @Result int
EXEC @Result =
sp_getapplock @Resource = 'holdit1', @LockMode = 'Exclusive', @LockTimeout = 10000 --Time to wait for the lock
IF @Result < 0
BEGIN
ROLLBACK TRAN
RAISERROR('Procedure Already Running for holdit1 - Concurrent execution is not supported.',16,9)
RETURN(-1)
END

, где 'holdit1' это просто имя длязамок.@result возвращает 0 или 1, если ему удается получить блокировку (одна из них - когда она сразу же срабатывает, а другая - когда вы получаете блокировку во время ожидания)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...