在表复合键中增加版本号
Posted
技术标签:
【中文标题】在表复合键中增加版本号【英文标题】:Incrementing revision numbers in table's composite key 【发布时间】:2016-04-10 00:18:27 【问题描述】:我正在本地运行 SQL Server 2014,以获取将部署到 Azure SQL V12 数据库的数据库。
我有一个存储业务实体对象的可扩展属性值的表,在本例中,三个表如下所示:
CREATE TABLE Widgets (
WidgetId bigint IDENTITY(1,1),
...
)
CREATE TABLE WidgetProperties (
PropertyId int IDENTITY(1,1),
Name nvarchar(50)
Type int -- 0 = int, 1 = string, 2 = date, etc
)
CREATE TABLE WidgetPropertyValues (
WidgetId bigint,
PropertyId int,
Revision int,
DateTime datetimeoffset(7),
Value varbinary(255)
CONSTRAINT [PK_WidgetPropertyValues] PRIMARY KEY CLUSTERED (
[WidgetId] ASC,
[PropertyIdId] ASC,
[Revision] ASC
)
)
ALTER TABLE dbo.WidgetPropertyValues WITH CHECK ADD CONSTRAINT FK_WidgetPropertyValues_WidgetProperties FOREIGN KEY( PropertyId )
REFERENCES dbo.WidgetProperties ( PropertyId )
ALTER TABLE dbo.WidgetPropertyValues WITH CHECK ADD CONSTRAINT FK_WidgetPropertyValues_Widgets FOREIGN KEY( WidgetId )
REFERENCES dbo.Widgets ( WidgetId )
所以你看到WidgetId, PropertyId, Revision
是一个复合键,并且表存储了Values的整个历史(当前值是通过为每个WidgetId + PropertyId
获取具有最大Revision
数字的行来获得的。
我想知道如何将Revision
列设置为每个WidgetId + PropertyId
增加1。我想要这样的数据:
WidgetId, PropertyId, Revision, DateTime, Value
------------------------------------------------
1 1 1 123
1 1 2 456
1 1 3 789
1 2 1 012
IDENTITY
不起作用,因为它对表是全局的,同样适用于 SEQUENCE
对象。
更新我能想到一个使用INSTEAD OF INSERT
触发器的可能解决方案:
CREATE TRIGGER WidgetPropertyValueInsertTrigger ON WidgetPropertyValues
INSTEAD OF INSERT
AS
BEGIN
DECLARE @maxRevision int
SELECT @maxRevision = ISNULL( MAX( Revision ), 0 ) FROM WidgetPropertyValues WHERE WidgetId = INSERTED.WidgetId AND PropertyId = INSERTED.PropertyId
INSERT INTO WidgetPropertyValues VALUES (
INSERTED.WidgetId,
INSERTED.PropertyId,
@maxRevision + 1,
INSERTED.DateTime,
INSERTED.Value,
)
END
(与在INSERT
操作之前或之后运行的普通INSERT
-trigger 相比,INSTEAD OF INSERT
触发器在表上运行而不是任何 INSERT
操作)
我认为这将是并发安全的,因为所有INSERT
操作都有一个隐式事务,并且任何关联的触发器都在同一个事务上下文中执行,这应该意味着它是安全的。除非任何人都可以提出其他要求?
【问题讨论】:
如果您有IDENTITY
,您可以使用row_number()
窗口函数根据插入顺序枚举修订。
或者使用序列来执行@ConsiderMe 建议,而不向表本身添加字段
@Dai 如果您的表中已经有数据,您需要某种列才能分配正确的修订号。将此类信息存储在多用户环境中会因为并发性而导致问题。不过,有一些解决方法,包括将正在应用修订的相关部分存储在另一个表中并获取行级锁。
@ConsiderMe 实际上,这没有实际意义。我决定使用非连续的 Revision
值并改用 IDENTITY
。
@Dai 就触发解决方案而言,选择和插入之间存在时间间隔 - 这就是它可能中断的原因。
【参考方案1】:
您的代码存在竞争条件 - 并发事务可能会在您的 SELECT 和 INSERT 之间选择并插入相同的修订。这可能会导致并发环境中的偶尔(主要)密钥违规(迫使您重试整个事务)。
与其重试整个事务,更好的策略是仅重试 INSERT。只需将您的代码放入一个循环中,如果发生键违规(并且仅键违规),请增加 Revision 并重试。
类似这样的东西(从我的脑海中写出来):
DECLARE @maxRevision int = (
SELECT
@maxRevision = ISNULL(MAX(Revision), 0)
FROM
WidgetPropertyValues
WHERE
WidgetId = INSERTED.WidgetId
AND PropertyId = INSERTED.PropertyId
);
WHILE 0 = 0 BEGIN
SET @maxRevision = @maxRevision + 1;
BEGIN TRY
INSERT INTO WidgetPropertyValues
VALUES (
INSERTED.WidgetId,
INSERTED.PropertyId,
@maxRevision,
INSERTED.DateTime,
INSERTED.Value,
);
BREAK;
END TRY
BEGIN CATCH
-- The error was different from key violation,
-- in which case we just pass it back to caller.
IF ERROR_NUMBER() <> 2627
THROW;
-- Otherwise, this was a key violation, and we can let the loop
-- enter the next iteration (to retry with the incremented value).
END CATCH
END
【讨论】:
以上是关于在表复合键中增加版本号的主要内容,如果未能解决你的问题,请参考以下文章