【问题标题】:Using a trigger to simulate a second identity column in SQL Server 2005使用触发器模拟 SQL Server 2005 中的第二个标识列
【发布时间】:2010-08-27 12:14:24
【问题描述】:

除了标识列 PK 之外,我有多种原因需要在 SQL Server 2005 数据库中实现第二个并发安全的自动递增列。能够拥有多个标识列将是理想的,但我正在考虑使用触发器来模拟这一点,尽可能接近金属。

我相信我必须在触发器中使用可序列化的隔离级别事务。我是否会像在普通 SQL 查询中使用这样的事务那样去做?

第二个增量列的业务含义与第一个,PK,增量列的幕后含义保持分离是一个不可协商的要求。

简单地说,如果我创建 JobCards '0001'、'0002' 和 '0003',然后删除 JobCards '0002' 和 '0003',我创建的下一个 Jobcard 必须有 ID '0002 ',而不是 '0004'。

【问题讨论】:

    标签: sql-server


    【解决方案1】:

    只是一个想法,如果您有 2 个“身份”列,那么它们肯定会“同步” - 如果不完全相同的值,那么它们会相差一个恒定值。如果是这样,那么为什么不将“第二个身份”列添加为COMPUTED column,它会抵消主要身份?还是我的逻辑有缺陷?

    编辑:根据 Martin 的评论,请注意您的 calc 可能需要为 N * id + C,其中 N 是增量,C 是偏移量/增量 - 请原谅我生锈的数学。

    例如:

    ALTER TABLE MyTable ADD OtherIdentity AS Id * 2 + 1;
    

    编辑 请注意,对于 Sql 2012 及更高版本,您现在可以使用 independent sequence 在同一个表中创建两个或更多独立递增的列。

    注意:OP 已将原始要求编辑为包括回收序列(注意 SQL 中的标识列一旦删除就不会回收已使用的 ID)。

    【讨论】:

    • 该死的——你打字比我快。 +1 为优秀的建议。
    • 也许他们有不同的增量?我敢肯定OP一定有他的理由!
    • 但是仍然可以从另一个计算一个。我承认我也很好奇其中的原因。
    • @LeandroTupone 嗨 - 如果没有 PERSISTED,计算列将在读取时进行评估,因此该值将绑定到底层 source 列(上面示例中的 Id) .所以只要Id 是稳定的(例如INT IDENTITY,那么并发应该不会有很多问题)。但肯定的是,在 2012 年及以后,如果第二个值是整数,请再找一个 SEQUENCE
    • 可以使用BIGINT as an IDENTITY column。请注意 Sql BIGINT 是 64 位 - 它与 .Net 或 Java 中的任意长度整数不同。
    【解决方案2】:

    我将完全禁止从此表中删除所有内容。我不会删除,而是将行标记为可用或非活动。我不会插入,而是首先搜索是否有非活动行,如果存在则重用 ID 最小的行。只有当表中没有可用的行时,我才会插入。

    当然,我会使用 sp_getapplock 序列化所有插入和删除操作。

    您可以使用触发器来禁止所有删除,这比填补空白更简单。

    【讨论】:

    • 我正在这样做,但我的要求是“如果记录被删除”,可能不是由用户,他们只能有效地将它们标记为非活动,但可能由支持人员或开发人员。
    • 您可以使用触发器来禁止所有删除,这比填补空白更简单。
    【解决方案3】:

    “Microsoft SQL Server 2008 内部:T-SQL 查询”中解决此问题的方法是创建另一个表,其中包含保存当前最大值的单行。

    CREATE TABLE dbo.Sequence(
     val int 
     )
    

    然后为您的插入分配足够大小的范围

    CREATE PROC dbo.GetSequence
    @val AS int OUTPUT,
    @n as int =1
    AS
    UPDATE dbo.Sequence 
    SET @val = val = val + @n;
    
    SET @val = @val - @n + 1; 
    

    这将阻止其他并发尝试增加序列,直到第一个事务提交。

    对于不处理多行插入的非阻塞解决方案see my answer here

    【讨论】:

    • 我被告知不要使用另一张桌子。
    • @ProfK 这无论如何都不适用于您明确的要求。
    【解决方案4】:

    这可能是一个糟糕的想法,但它至少在有限的使用场景中有效

    只需使用常规身份并在删除时重新设置种子。

    create table reseedtest (
       a int identity(1,1) not null,
       name varchar(100)
    )
    
    insert reseedtest values('erik'),('john'),('selina')
    select * from reseedtest
    
    go
    CREATE TRIGGER TR_reseedtest_D ON reseedtest FOR DELETE
    AS
    BEGIN TRAN
    DECLARE @a int
    SET @a = (SELECT TOP 1 a FROM reseedtest WITH (TABLOCKX, HOLDLOCK))
    --anyone know another way to lock a table besides doing something to it?
    DBCC CHECKIDENT(reseedtest, reseed, 0)
    DBCC CHECKIDENT(reseedtest, reseed)
    COMMIT TRAN
    GO
    
    delete reseedtest where a >= 2
    insert reseedtest values('katarina'),('david')
    select * from reseedtest
    
    drop table reseedtest
    

    如果您从“堆栈中间”删除,这将不起作用,但它适用于从递增端删除。

    一次重新设置为 0 然后再次设置只是避免计算正确重新设置值的技巧。

    【讨论】:

    • 它无法工作。如果你只删除中间的一行,重新设定你的身份,然后添加两行,你会得到重复的。
    • @AlexKuznetsov - 这就是我所说的,请仔细阅读。这可能不是最好的答案,但您必须承认这是一种有趣的技术。这是极简主义并解决了问题。此外,OP还没有说他是否需要处理中间的间隙。他给定的场景只显示最后删除行。
    • @nonnb 必须在某个地方发生锁定才能使两个会话不计算相同的值。表锁是不幸的,但是如果有人在两个 DBCC 语句之间插入会发生什么?老实说,我不知道如何将当前标识值转换为变量,即使我这样做了,DBCC CHECKIDENT 是否接受变量作为第三个参数?
    • @AlexKuznetsov 我刚刚重读了你所说的,并意识到它是不正确的。如果您删除中间的一行并将标识重新设置到最后,您将不会得到重复的行,您只会有一个洞。仔细查看我的重新种子代码:它会自动调整为在上次使用的标识值之后开始。
    • @Emtucifor:同意,它不会工作,因为会有一个洞,而不是因为会有重复。您是否测试过您的方法在高并发下的工作方式?
    【解决方案5】:

    如果您从不从表中删除,您可以创建一个包含使用 ROW_NUMBER() 的具体化列的视图。

    此外,SQL Server 身份可能与用户生成的身份不同步,具体取决于回滚的使用。

    【讨论】:

      猜你喜欢
      • 2011-06-08
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-25
      • 2010-09-14
      • 2010-09-18
      • 1970-01-01
      相关资源
      最近更新 更多