Этот вопрос, конечно, относится к намного более широкому объему, но здесь это.
У меня есть основное приложение электронной коммерции, где пользователи могут, достаточно естественно, разместить заказы. Упомянутые заказы должны иметь уникальное число, которое я пытаюсь генерировать прямо сейчас.
Каждый порядок является Определенным для поставщика. В основном я имею OrderNumberInfo (VendorID, OrderNumber)
таблица. Теперь каждый раз, когда клиент размещает заказ, я должен увеличить OrderNumber
для particuar Поставщика и возвращают то значение. Естественно, я не хочу, чтобы другие процессы вмешались в меня, таким образом, я должен исключительно заблокировать эту строку так или иначе:
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)
, пессимистическая блокировка, и т.д., но просто не может приспособить все это в четкой картине:
Править
Только сделать немного вещей более ясными:
Обычно я использую что-то вроде этого:
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 году.
Ваше решение создаст потенциальное узкое место для производительности в таблице OrderNumberInfo
.
Есть ли какая-то конкретная причина, по которой заказы не могут быть просто столбцом идентификации, возможно, с префиксом идентификатора поставщика на стороне приложения (например, MSFT-232323)?
Единственный недостаток этого подхода заключается в том, что для каждого поставщика Заказы не будут шаблоном «Добавить-1-для-получения-следующего-заказа- #», но мне не известно о каких-либо технических или деловых соображениях, объясняющих, почему это может вызвать проблему, хотя это может привести к порядку в последовательности обработка немного сложнее.
Они все равно будут увеличиваться и быть уникальными для каждого поставщика, что является единственным реальным требованием для идентификатора заказа.
Это, конечно, будет иметь дополнительное побочное преимущество в виде очень простой логики, не зависящей от поставщика (если она у вас когда-либо будет) - например, контроль качества / отчетность в масштабе всего приложения.
Вы можете использовать предложение 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
имеет уникальное ограничение или, по крайней мере, будет только одна строка для каждого идентификатора поставщика. Если это не так, вы потенциально будете обновлять и / или возвращать несколько строк, что не кажется хорошей идеей!