无间隙标识栏策略

时间:2016-12-25 14:49:28

标签: sql sql-server

我在身份栏的手中遇到了很多问题,我不想再使用它了。我想要一个不同的策略。最终,我想要一个无间隙的整数列,它们在每个行插入上按顺序递增。我不知道如何实现这一目标。是否可以从包含整数序列的表格或在运行中创建它们的函数中绘制?感谢您帮助我解决这个技术问题。

5 个答案:

答案 0 :(得分:9)

为什么你想要一个无间隙的值序列?

SQL Server(以及一般数据库)不能轻易支持此功能的原因是因为它很难做到。从本质上讲,您必须锁定整个表格以进行插入,从而减慢所有内容。

通常,拥有identity主键就足够了。虽然您会得到间隙,但您可以使用以下方法轻松计算序列:

select row_number() over (order by <pkid>)

其中<pkid>是标识,主键列。

如果你坚持无间隙,那么你需要实现一个触发器并自己进行计算。我认为一个序列(没有缓存)就足以生成下一个数字。但是,你需要非常小心,关于插入失败。

答案 1 :(得分:1)

差距是一个纯粹的逻辑问题。

假设插入操作可能失败(并且可以),
任何2个并发插入操作都可能导致间隙,例如 -

上次使用的值是13.

会话1的插入操作使用14.
会话2的插入操作使用15.
会话1中的插入操作失败(回滚) 会话2中的插入操作成功(提交)。

13到15之间存在差距。

由于这是一个逻辑问题,您无法使用代码绕过它,您仍需要对插入操作进行serielize
如果序列化操作失败,使用您自己的代码唯一可以实现的就是没有差距。

答案 2 :(得分:1)

Gordon Linoff向您的方法阐述了核心问题。既然不清楚你需要什么样的无间隙身份,我们只能猜测。出于查询目的,您应该使用正确的声明。考虑ROWNUMBER()LEAD()和LAG()。如果您需要经常查询,可以打包已经建议的 row_number()结束(按ID排序) 例如,在名为gaplessID的计算列中。其他替代方案是:

  • 有一个维护作业,它会删除索引约束,根据ROWNUMBER更新ID列,然后读取索引

  • 将逻辑实现到您的控制层并使用标识插入

  • 使用复杂而不是触发器,由于性能不佳我不推荐

答案 3 :(得分:1)

[1]&#34;我想要一个无间隙的整数列,它们在每一行插入上按顺序递增&#34;

这可以通过一个表来轻松实现,以存储每个表的最后一个ID值&#34; custom&#34; IDentity栏:

CREATE TABLE dbo.CustomSequence (
    FullTableName NVARCHAR(257) NOT NULL
        CONSTRAINT PK_CustomSequence_FullTableName
        PRIMARY KEY(FullTableName), -- This means a unique clustered index on this column
    LastID INT NULL,
    Start INT NOT NULL,
    Seed INT NOT NULL
)

对于每个表/列,使用&#34; custom&#34; ID我会插入一行:

INSERT dbo.CustomSequence (FullTableName, LastID, Start, Seed)
VALUES 
(N'dbo.Invoice', NULL, 1, 1),
(N'dbo.InvoiceDetail', NULL, 1, 1);

然后生成没有间隙的标识值(!)我将首先使用一个Tx(事务)和UPDATE语句:

SET XACT_ABORT ON;
BEGIN TRAN;
    ...
    -- Generating new "custom" IDs
    DECLARE @InvNumOfIDs INT = 1, @InvFirstGeneratedIdentityValue INT;
    UPDATE cs
    SET @InvFirstGeneratedIdentityValue = LastID = IIF(cs.LastID IS NULL, cs.Start + (@InvNumOfIDs - 1) * cs.Seed, cs.LastID + @InvNumOfIDs * cs.Seed)
    FROM dbo.CustomSequences cs
    WHERE cs.FullTableName = N'dbo.Invoice';
    ...
    -- Using above IDs
    INSERT dbo.Invoice (InvoiceID, CreateDate, ...)
    VALUES (@InvFirstGeneratedIdentityValue, GETDATE(), ...)
    ...
COMMIT;

注意:在同一个Tx中封装两个语句(生成自定义ID - UPDATE和使用新ID - INSERT)非常重要。因此,如果最后一个语句 - INSERT - 失败,则整个Tx应该失败,并且ROLLBACK也将由第一个语句 - UPDATE - 进行更改,从而生成新ID。

注意#2:SET XACT_ABORT ON的标准行为是在出现错误/异常时自动回滚Tx。

注意#3:问:什么时候应该按预期工作?答:在以下事务隔离级别下:READ UNCOMMITTED,READ COMMITTED(SQL Server的默认隔离级别),REPETABLE READ和SERIALIZABLE。

注意#4:问:为什么A:假设有两个或更多并发Tx(参见上面模板)试图将行插入同一个表(例如dbo.Invoice),第一个Tx将成功获得[e]X[clusive] lock关于dbo.Invoice&#39;来自dbo.CustomSequence表的行,它将保持此X loc k直到当前Tx结束 - >尝试执行UPDATE语句的所有其他并发Tx(为dbo.Invoice表生成新ID)将被阻止,并且必须等到第一个Tx结束(使用COMMIT或{{ 1}})。

基本上,这意味着尝试将行插入同一目标表(例如ROLLBACK)的并发Tx被序列化 - &gt;在同一目标表上没有并发INSERT(在此示例中为例如dbo.Invoice)。

注意#5:问:如果当前Tx(第一个Tx)失败会发生什么?答:在这种情况下,应该取消当前的Tx(ROLLBACK) - &gt; dbo.Invoice 也将被取消(ROLLBACK) - &gt;将在先前Tx的ID与下一个Tx的ID之间没有间隙。

注意#6:问:如果当前Tx(第一个Tx)成功结束会怎样?答:在这种情况下,当前的Tx应该是COMMITed - &gt;更新后的更改dbo.CustomSequence&#39;变得永久 - &gt;将在先前Tx的ID与下一个Tx的ID之间没有间隙。

注意#7:问:如果这两个语句(UPDATE dbo.CustomSequenceUPDATE dbo.CustomSequence)使用单独的Tx会发生什么?如果INSERT失败,我们无法回滚INSERT dbo.Invoice - &gt;的间隙。

注意#8:问:如果一个Tx删除一行会发生什么 - &gt;的间隙。

注意#9:如果不够清楚:试图将行插入UPDATE dbo.CustomSequencedbo.Invoice表的所有Tx应遵循相同的模板/方法。

[2]内置身份 vs。 Gap-less&#34; custom&#34;身份(见上文)

dbo.InvoiceDetail

[3]我没有测试过上面的源代码,但我在一些项目中使用了类似的方法。

答案 4 :(得分:0)

感谢您提供的评论和答案。为了满足那里的哲学家,我将解释为什么我想要一个无间隙的领域。它不是基于任何需要,它不是需要它的脚本,而且它不是生产应用程序。我有一个个人数据库,我正在生活,有时服务会中断,我重新启动计算机,或者身份发生差距的任何其他原因。在一个参考表中,我有47条记录,最大标识是2024.它在其他表上失去控制。

除了未来的证明,我很好奇。当我看到这样的问题,其中有大量的帖子,我想找到一个解决方案或最佳实践。自从我提出这个问题以来,我进行了更多的研究,并试图使用序列。在做了一些实验之后,我已经确定我的应用程序的性能是可以接受的。为了从身份字段切换到序列,我制定了以下策略:

1个重复的数据库(出于测试目的,我对dup进行了所有更改) 2为每个id字段创建一个序列(从1开始加1除无缓存) 3更改表以添加新的id字段(因为我无法从字段中删除Identity属性)并将默认值添加为NEXT VALUE FOR序列 4使用NEXT VALUE FOR序列更新每个引用表上的新id字段 5对于任何具有引用步骤4中的一个表的外键的表,我在旧的id字段上进行了连接,并使用参考表中的新字段更新了外键字段
6更改了表以将新字段更改为非null 7删除了外键和主键 8掉了旧的id字段 9将新字段sp_rename到旧名称 10重新创建了主键和外键

我无法以编程方式执行的唯一部分是重新排序字段,这是我在SSMS中的Designer中所做的。展望未来,我将在我的数据库中使用序列。我对解决方案非常满意。