我在身份栏的手中遇到了很多问题,我不想再使用它了。我想要一个不同的策略。最终,我想要一个无间隙的整数列,它们在每个行插入上按顺序递增。我不知道如何实现这一目标。是否可以从包含整数序列的表格或在运行中创建它们的函数中绘制?感谢您帮助我解决这个技术问题。
答案 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.CustomSequence
和UPDATE dbo.CustomSequence
)使用单独的Tx会发生什么?如果INSERT失败,我们无法回滚INSERT dbo.Invoice
- &gt;的间隙。
注意#8:问:如果一个Tx删除一行会发生什么 - &gt;的间隙。
注意#9:如果不够清楚:试图将行插入UPDATE dbo.CustomSequence
或dbo.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中所做的。展望未来,我将在我的数据库中使用序列。我对解决方案非常满意。