确保 SQL Server 数据库中的唯一编号
我有一个使用事件编号(以及其他类型的数字)的应用程序。这些数字存储在名为“Number_Setup”的表中,其中包含计数器的当前值。
当应用程序生成新事件时,它会在 number_setup 表中获取所需的数字计数器行(计数器可以每天、每周等重置,并存储为 int)。然后它递增计数器并用新值更新该行。
该应用程序是多用户的(每次大约有 100 个用户,以及运行并获取 100 个事件记录并请求每个事件编号的 SQL 作业)。事件表中有一些重复的事件编号,它们不应重复。
存储过程用于检索下一个计数器。
SELECT @Counter = counter, @ShareId=share_id, @Id=id FROM Number_Setup WHERE LinkTo_ID=@LinkToId AND Counter_Type='I' IF isnull(@ShareId,0) > 0 BEGIN -- use parent counter SELECT @Counter = counter, @ID=id FROM Number_Setup WHERE Id=@ShareID END SELECT @NewCounter = @Counter + 1 UPDATE Number_Setup SET Counter = @NewCounter WHERE id=@Id
我现在已经用事务包围了该块,但我不完全确定它会 100% 解决问题,因为我认为仍然存在共享锁,因此无论如何都可以读取计数器。
也许我可以检查计数器是否尚未更新,在更新声明中
UPDATE Number_Setup SET Counter = @NewCounter WHERE Counter = @Counter IF @@ERROR = 0 AND @@ROWCOUNT > 0 COMMIT TRANSACTION ELSE ROLLBACK TRANSACTION
我确信这是金融应用程序等中发票号码的常见问题。
我也无法将逻辑放入代码中并在该级别使用锁定。 我也锁定了 HOLDLOCK,但我不确定它的应用。是否应该放在两个 SELECT 语句上?
如何确保不会创建重复项?
I have an application that uses incident numbers (amongst other types of numbers). These numbers are stored in a table called "Number_Setup", which contains the current value of the counter.
When the app generates a new incident, it number_setup table and gets the required number counter row (counters can be reset daily, weekly, etc and are stored as int's). It then incremenets the counter and updates the row with the new value.
The application is multiuser (approximately 100 users at any one time, as well as sql jobs that run and grab 100's of incident records and request incident numbers for each). The incident table has some duplicate incident numbers where they should not be duplicate.
A stored proc is used to retrieve the next counter.
SELECT @Counter = counter, @ShareId=share_id, @Id=id FROM Number_Setup WHERE LinkTo_ID=@LinkToId AND Counter_Type='I' IF isnull(@ShareId,0) > 0 BEGIN -- use parent counter SELECT @Counter = counter, @ID=id FROM Number_Setup WHERE Id=@ShareID END SELECT @NewCounter = @Counter + 1 UPDATE Number_Setup SET Counter = @NewCounter WHERE id=@Id
I've now surrounded that block with a transaction, but I'm not entirely sure it' will 100% fix the problem, as I think there's still shared locks, so the counter can be read anyway.
Perhaps I can check that the counter hasn't been updated, in the update statement
UPDATE Number_Setup SET Counter = @NewCounter WHERE Counter = @Counter IF @@ERROR = 0 AND @@ROWCOUNT > 0 COMMIT TRANSACTION ELSE ROLLBACK TRANSACTION
I'm sure this is a common problem with invoice numbers in financial apps etc.
I cannot put the logic in code either and use locking at that level.
I've also locked at HOLDLOCK but I'm not sure of it's application. Should it be put on the two SELECT statements?
How can I ensure no duplicates are created?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
诀窍是在单个原子操作中进行计数器更新和读取:
但这不会将新计数器分配给 @NewCounter,而是将其作为结果集返回给客户端。如果必须赋值,则使用中间表变量将新计数器输出 INTO:
这解决了使 Counter 增量原子化的问题。您的过程中仍然存在其他竞争条件,因为 LinkTo_Id 和 share_id 仍然可以在第一次选择后更新,因此您可以增加错误链接到项目的计数器,但这不能仅通过此代码示例来解决,因为它还取决于在实际更新shared_id和/或LinkTo_Id的代码上。
顺便说一句,您应该养成使用一致大小写命名字段的习惯。如果它们的命名一致,那么您必须在 T-SQL 代码中使用完全匹配的大小写。您的脚本现在运行良好,只是因为您有一个不区分大小写的排序规则服务器,如果您部署在区分大小写的排序规则服务器上,并且您的脚本与字段/表名称的确切大小写不匹配,则会出现大量错误。
The trick is to do the counter update and read in a single atomic operation:
This though does not assign the new counter to @NewCounter, but instead returns it as a result set to the client. If you have to assign it, use an intermediate table variable to output the new counter INTO:
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.
您是否尝试过使用 GUID 而不是自动增量作为唯一标识符?
have you tried using GUIDs instead of autoincrements as your unique identifier?
如果您有能力修改获取多个记录的工作,我会改变想法,使您的计数器成为身份列。然后,当您获取下一条记录时,您可以执行插入并获取表的@@identity。这将确保您获得最大的数字。当您想要重置身份时,您还必须执行 dbccReseed 来重置计数器,而不仅仅是更新表。唯一的问题是,作为 sql 作业的一部分,您必须执行 100 次左右的插入才能获取一组身份。这可能开销太大,但使用标识列是获取唯一数字的有保证的方法。
If you have the ablity to modify your job that gets mutiple records, I would change the thinking so that your counter is an identity column. Then when you get the next record you can just do an insert and get the @@identity of the table. That would ensure that you get the biggest number. You would also have to do a dbccReseed to reset the counter instead of just updating the table when you want to reset the identity. The only issue is that you'd have to do 100 or so inserts as part of your sql job to get a group of identities. That may be too much overhead but using an identity column is a guarenteed way to get unique numbers.
我可能遗漏了一些东西,但看起来你正在尝试重新发明大多数数据库已经解决的技术。
为什么不为计数器使用自动增量主键,而不是从 Number_Setup 表中的“计数器”列读取和更新呢?主键永远不会有重复值。
I might be missing something, but it seems like you are trying to reinvent technology that has already been solved by most databases.
instead of reading and updating from the 'Counter' column in the Number_Setup table, why don't you just use an autoincrementing primary key for your counter? You'll never have a duplicate value for a primary key.