1

我有一个使用事件编号(以及其他类型的编号)的应用程序。这些数字存储在名为“Number_Setup”的表中,其中包含计数器的当前值。

当应用程序生成一个新事件时,它会在 number_setup 表中获取所需的数字计数器行(计数器可以每天、每周等重置并存储为 int)。然后它递增计数器并使用新值更新行。

该应用程序是多用户的(任何时候大约有 100 个用户,以及运行并获取 100 条事件记录并为每个请求事件编号的 sql 作业)。事件表有一些重复的事件编号,它们不应重复。

存储过程用于检索下一个计数器。

选择 @Counter = 计数器,@ShareId=share_id,@Id=id
FROM Number_Setup
WHERE LinkTo_ID=@LinkToId
AND Counter_Type='我'

IF isnull(@ShareId,0) > 0
开始
    -- 使用父计数器
    选择 @Counter = 计数器,@ID=id
    FROM Number_Setup
    哪里 ID=@ShareID
结尾

选择 @NewCounter = @Counter + 1

更新 Number_Setup 设置计数器 = @NewCounter
哪里 id=@Id

我现在已经用事务包围了那个块,但我不完全确定它会 100% 解决问题,因为我认为仍然存在共享锁,所以无论如何都可以读取计数器。

也许我可以在更新语句中检查计数器是否已更新

更新 Number_Setup 设置计数器 = @NewCounter
WHERE 计数器 = @Counter
如果@@ERROR = 0 并且@@ROWCOUNT > 0
    提交交易
别的
    回滚交易

我确信这是财务应用程序等中发票号码的常见问题。
我也无法将逻辑放入代码中并在该级别使用锁定。我也锁定在 HOLDLOCK 但我不确定它的应用程序。它应该放在两个 SELECT 语句上吗?

如何确保不创建重复项?

4

4 回答 4

4

诀窍是在单个原子操作中进行计数器更新和读取:

UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter 
WHERE id=@Id;

这虽然不会将新计数器分配给@NewCounter,而是将其作为结果集返回给客户端。如果必须分配它,请使用中间表变量来输出新的计数器 INTO:

declare @NewCounter int;
declare @tabCounter table (NewCounter int);
UPDATE Number_Setup SET Counter = Counter+1
OUTPUT INSERTED.Counter INTO @tabCounter (NewCounter)
WHERE id=@Id
SELECT @NewCounter = NewCounter FROM @tabCounter;

This solves the problem of making the Counter increment atomic. You still have other race conditions in your procedure because the LinkTo_Id and share_id can still be updated after the first select so you can increment the counter of the wrong link-to item, but that cannot be solved just from this code sample as it depends also on the code that actualy updates the shared_id and/or LinkTo_Id.

BTW you should get into the habbit of name your fields with consistent case. If they are named consistently then you must use the exact match case in T-SQL code. Your scripts run fine now just because you have a case insensitive collation server, if you deploy on a case sensitive collation server and your scripts don't match the exact case of the field/tables names errors will follow galore.

于 2009-10-08T23:27:05.893 回答
0

您是否尝试过使用 GUID 而不是自动增量作为您的唯一标识符?

于 2009-10-08T22:44:30.807 回答
0

如果您有能力修改获得多条记录的工作,我会改变想法,使您的计数器成为一个身份列。然后,当您获得下一条记录时,您可以进行插入并获得表的@@identity。这将确保您获得最大的数字。您还必须执行 dbccReseed 来重置计数器,而不是在您想要重置身份时仅更新表。唯一的问题是,作为 sql 作业的一部分,您必须执行 100 次左右的插入操作才能获得一组身份。这可能开销太大,但使用标识列是获得唯一数字的保证方法。

于 2009-10-08T22:53:28.713 回答
0

我可能遗漏了一些东西,但您似乎正在尝试重新发明大多数数据库已经解决的技术。

而不是从 Number_Setup 表中的“计数器”列读取和更新,为什么不直接为计数器使用自动递增主键?您永远不会有主键的重复值。

于 2009-10-08T22:55:06.707 回答