Правильный способ создания номеров заказов в SQL Server - PullRequest
7 голосов
/ 07 июня 2010

Этот вопрос, безусловно, относится к гораздо более широкой области, но вот он.

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

Каждый заказ зависит от поставщика. По сути, у меня есть таблица OrderNumberInfo (VendorID, OrderNumber). Теперь, когда клиент размещает заказ, мне нужно увеличить OrderNumber для конкретного Продавца и вернуть это значение. Естественно, я не хочу, чтобы другие процессы мешали мне, поэтому мне нужно как-то заблокировать эту строку исключительно:

begin tranaction

    declare @n int
    select @n = OrderNumber 
      from OrderNumberInfo 
      where VendorID = @vendorID

    update OrderNumberInfo 
      set OrderNumber = @n + 1 
      where OrderNumber = @n and VendorID = @vendorID

commit transaction

Теперь я прочитал о select ... with (updlock rowlock), пессимистической блокировке и т. Д., Но просто не могу уместить все это в связную картину:

  • Как эти подсказки взаимодействуют с изоляцией моментальных снимков в SQL Server 2008?
  • Они выполняют блокировки на уровне строк, страниц или даже на уровне таблиц?
  • Как это допустимо для нескольких пользователей, пытающихся сгенерировать числа для одного поставщика?
  • Какие уровни изоляции здесь уместны?
  • И вообще - как это делать?

EDIT

Просто чтобы кое-что прояснить:

  • Производительность в этом конкретном углу приложения абсолютно не проблема: заказы будут размещаться относительно редко и будут связаны с дорогостоящим вызовом веб-службы поставщиков, поэтому задержка в 1 секунду довольно терпима
  • Нам действительно необходимо, чтобы номера заказов каждого поставщика были независимыми и последовательными

Ответы [ 5 ]

3 голосов
/ 07 июня 2010

Ваше решение создаст потенциальное узкое место для производительности в таблице OrderNumberInfo.

Существует ли какая-либо конкретная причина, по которой заказы не могут просто представлять собой столбец идентификаторов, возможно с префиксом с идентификатором поставщика на стороне приложения (например, MSFT-232323)?

Единственным недостатком этого подхода является то, что заказы для каждого поставщика не будут являться шаблоном «Добавить-1-получить-следующий-заказ-№», но я незнать о каких-либо технических или деловых соображениях о том, почему это может представлять проблему, хотя это может усложнить последовательную обработку заказов.

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

Это, конечно, даст дополнительное преимущество, заключающееся в очень легкой независимости от поставщика.логика, предполагающая, что она у вас когда-либо есть) - например, контроль качества приложений / отчетность.

3 голосов
/ 07 июня 2010

Вы можете использовать предложение OUTPUT. Это должно делать все атомарно, не требуя транзакции.

-- either return the order number directly as a single column resultset
UPDATE OrderNumberInfo 
SET OrderNumber = OrderNumber + 1
    OUTPUT DELETED.OrderNumber
WHERE VendorID = @vendorID


-- or use an intermediate table variable to get the order number into @n
DECLARE @n INT
DECLARE @temp TABLE ( OrderNumber INT )

UPDATE OrderNumberInfo 
SET OrderNumber = OrderNumber + 1
    OUTPUT DELETED.OrderNumber
    INTO @temp ( OrderNumber )
WHERE VendorID = @vendorID

SET @n = (SELECT TOP 1 OrderNumber FROM @temp)

В приведенных выше примерах предполагается, что столбец VendorID имеет уникальное ограничение, или, по крайней мере, будет только одна строка на идентификатор поставщика. Если это не так, то вы потенциально будете обновлять и / или возвращать несколько строк, что не кажется хорошей идеей!

1 голос
/ 07 июня 2010

Я обычно использую что-то вроде этого:

update OrderNumberInfo with (rowlock)
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1
where VendorID = @VendorID

Не нужно заключать в транзакцию. Фактически, если вы включите его в транзакцию, SQL Server начнет удерживать блокировки таблицы и замедлит все. Когда мне нужно сделать что-то подобное в веб-сервисе, я всегда выполняю его на отдельном соединении с базой данных вне любой транзакции, которая может быть открыта в данный момент, просто чтобы убедиться.

Я считаю (но не доказал), что SQL Server использует защелку, а не транзакцию, чтобы сделать ее атомарной, что должно быть более эффективным.

Если ваш дизайн таблицы таков, что строка поставщика должна быть создана по требованию, если она не существует, используйте вместо этого следующую логику:

declare @error int, @rowcount int

-- Attempt to read and update the number.
update OrderNumberInfo with (rowlock)
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1
where VendorID = @VendorID

select @error = @@error, @rowcount = @@rowcount
if @error <> 0 begin
    return @error
end

-- If the update succeeded then exit now.
if @rowcount > 0 begin
    return 0
end

-- Insert the row if it doesn't exist yet.
insert into OrderNumberInfo (VendorID, OrderNumber)
select VendorID, 1
where not exists (select null from OrderNumberInfo where VendorID = @VendorID)

select @error = @@error
if @error <> 0 begin
    return @error
end

-- Attempt to read and update the number.
update OrderNumberInfo with (rowlock)
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1
where VendorID = @VendorID

select @error = @@error
if @error <> 0 begin
    return @error
end

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

Отказ от ответственности: Я использовал это без проблем на SQL Server 7-2005. Я пока не могу комментировать его поведение в 2008 году.

0 голосов
/ 28 июня 2013

вот оно:

объявляют @C int = 0; обновить набор таблицы Code = @ C, @ C = @ C + 1

0 голосов
/ 28 октября 2011

Способ сделать это для поддержания согласованности:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
declare @n int
select @n = OrderNumber 
  from OrderNumberInfo 
  where VendorID = @vendorID

update OrderNumberInfo 
  set OrderNumber = @n + 1 
  where OrderNumber = @n and VendorID = @vendorID

COMMIT TRANSACTION

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

...