我们正在开发一个电子商务系统,汇总来自不同卖家的订单。您可以很容易地想象我们有一个Order
表来保存所有卖家的订单数据。每个卖家都有一个唯一的AccountID
,它是Order
表格中的外键。
我们希望为进入系统的每个订单生成一个订单号,以便对于给定的卖家(并且给定AccountID
),这些订单号创建一个序列(第一个订单获得1,然后是2,然后3等)。
我们尝试了几种解决方案,但它们有我们想要避免的缺点。所有这些都在触发器内:
ALTER TRIGGER [dbo].[Trigger_Order_UpdateAccountOrderNumber]
ON [dbo].[Order]
AFTER INSERT
BEGIN
...
END
我们的解决方案1 是:
UPDATE
[Order]
SET
AccountOrderNumber = o.AccountOrderNumber
FROM
(
SELECT
OrderID,
AccountOrderNumber =
ISNULL((SELECT TOP 1 AccountOrderNumber FROM [Order] WHERE AccountID = i.AccountID ORDER BY AccountOrderNumber DESC), 1) +
(ROW_NUMBER() OVER (PARTITION BY i.AccountID ORDER BY i.OrderID))
FROM
inserted AS i
) AS o
WHERE [Order].OrderID = o.OrderID
请注意,我们有READ_COMMITTED_SNAPSHOT ON
。它似乎运行了一段时间,但最近我们注意到AccountOrderNumber
列中的一些重复值。在分析代码之后,似乎逻辑上可能出现重复项,因为操作不是原子操作,因此如果在同一时间添加2个订单,它们将从TOP 1
表中读取相同的Order
值。 / p>
在注意到重复项后,我们提出了解决方案2 ,其中我们有一个单独的表来跟踪每个AccountOrderNumber
的下一个Account
:
DECLARE @NewOrderNumbers TABLE
(
AccountID int,
OrderID int,
AccountOrderNumber int
}
在这种情况下触发器主体如下:
INSERT INTO @NewOrderNumbers (AccountID, OrderID, AccountOrderNumber)
SELECT
I.AccountID,
I.OrderID,
ASN.Number + (ROW_NUMBER() OVER (PARTITION BY I.AccountID ORDER BY I.OrderID))
FROM
inserted AS I
INNER JOIN AccountSequenceNumber ASN WITH (UPDLOCK) ON I.AccountID = ASN.AccountID AND ASN.AccountSequenceNumberTypeID = @AccountOrderNumberTypeID
UPDATE [Order] ...
虽然此解决方案没有创建任何重复项,但由于@NewOrderNumbers
确实导致新创建的WITH (UPDLOCK)
表上出现死锁。不幸的是,锁定是必要的,以避免重复。
我们最新的尝试( Solution 3 )是使用序列。为此,我们需要为系统中的每个Account
创建一个序列,然后在插入新订单时使用它。以下是为AccountID = 1
创建序列的代码:
CREATE SEQUENCE Seq_Order_AccountOrderNumber_1 AS INT START WITH 1 INCREMENT BY 1 CACHE 100
AccountID = 1
的触发器主体:
DECLARE @NumbersRangeToAllocate INT = (SELECT COUNT(1) FROM inserted);
DECLARE @range_first_value_output SQL_VARIANT;
EXEC sp_sequence_get_range N'Seq_Order_AccountOrderNumber_1', @range_size = @NumbersRangeToAllocate, @range_first_value = @range_first_value_output OUTPUT;
DECLARE @Number INT = CAST(@range_first_value_output AS INT) - 1;
UPDATE
o
SET
@Number = o.AccountOrderNumber = @Number + 1
FROM
dbo.[Order] AS b JOIN inserted AS i on o.OrderID = i.OrderID
使用序列的方法让我们担心,因为我们希望很快就会在系统中拥有100K +帐户,并且对于每个帐户,我们当前需要在6个不同的表中增加ID。这意味着我们最终会有数十万个序列,这可能会对整个数据库的性能产生负面影响。我们不知道它是否会产生任何影响,但几乎不可能在网上找到任何在SQL Server中使用过那么多序列的人的证词。
最后,问题是:你能想出更好的解决方案吗?看起来这应该是一个非常常见的用例,你需要一个ID,它对于外键的每个值都是单独增加的。也许我们错过了一些明显的东西?
我们也欢迎您就上述3种解决方案发表意见。也许其中一个接近可接受,只需要一些小的调整?
答案 0 :(得分:1)
可能有更聪明的方法来实现这一目标,但这是我的快速提案。创建一个维护最高帐户订单号的视图;使用schemabinding获取速度并使用视图插入记录。
查看定义:
create view dbo.vwMaxAccountOrderNumber
with schemabinding as
select o.AccountID,
max(o.AccountOrderNumber) as Sequence
from Orders as o
group by o.AccountID
如果你是insert...selecting
,那么将它用于插入语句:
insert into Orders (
OrderID,
AccountID,
AccountOrderNumber,
...
)
select SomeAutoIncrementValue,
AccountID,
vmaon.Sequence + row_number() (partition by a.AccountID order by SomeOtherColumn)
from Accounts as a
inner join dbo.vwMaxAccountOrderNumber as vmaon
on vmaon.AccountID = a.AccountID
inner join ...
或者insert...values
:
insert into Orders (
OrderID,
AccountID,
AccountOrderNumber,
...
)
values (
SomeAutoIncrementValue,
12,
(select Sequence + 1 from dbo.vwMaxAccountOrderNumber where AccountID = 12),
...
)