【问题标题】:Ensuring unique numbers from a sql server database确保来自 sql server 数据库的唯一编号
【发布时间】:2009-10-08 22:41:54
【问题描述】:

我有一个使用事件编号(以及其他类型的编号)的应用程序。这些数字存储在名为“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 UPDATE Number_Setup SET Counter = @NewCounter 哪里 id=@Id

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

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

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

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

如何确保不创建重复项?

【问题讨论】:

    标签: sql-server transactions rowlocking


    【解决方案1】:

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

    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;
    

    这解决了使计数器增量原子化的问题。您的过程中仍然存在其他竞争条件,因为 LinkTo_Id 和 share_id 在第一次选择后仍然可以更新,因此您可以增加错误链接到项目的计数器,但这不能仅通过此代码示例解决,因为它也取决于在实际更新 shared_id 和/或 LinkTo_Id 的代码上。

    顺便说一句,您应该养成用一致的大小写命名字段的习惯。如果它们一致命名,那么您必须在 T-SQL 代码中使用完全匹配大小写。您的脚本现在运行良好,只是因为您有一个不区分大小写的排序规则服务器,如果您在区分大小写的排序规则服务器上部署并且您的脚本与字段/表名称的确切大小写不匹配,则会出现大量错误。

    【讨论】:

    • 感谢您的反馈。架构不在我们的控制范围内,我的示例由字段名称组成,不是实际存储过程的直接副本 :)
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • scope_identity() 会比@@identity 更好地确保您取回刚刚创建的身份。
      【解决方案4】:

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

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

      【讨论】:

      • 因为计数器必须生成一个每天、每月或其他客户定义的周期重置的数字。不同客户的事件编号每天都会重置,有些则从不重置。事件编号被放置在事件表上的一个字段中。该字段不是唯一的,只是索引。现有的存储过程已经存在了 10 多年 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多