使用 COALESCE 将主键从 IDENTITY 更改为持久化计算列

Mr *_*ose 10 sql-server identity computed-column

为了将应用程序与我们的整体数据库分离,我们尝试将各种表的 INT IDENTITY 列更改为使用 COALESCE 的 PERSISTED 计算列。基本上,我们需要解耦应用程序能够更新数据库以获取跨多个应用程序共享的公共数据,同时仍然允许现有应用程序在这些表中创建数据,而无需修改代码或过程。

所以基本上,我们已经从列定义转移到了;

PkId INT IDENTITY(1,1) PRIMARY KEY
Run Code Online (Sandbox Code Playgroud)

到;

PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL
Run Code Online (Sandbox Code Playgroud)

在所有情况下,PkId 也是一个 PRIMARY KEY,在所有情况下,除了一种情况,它是 CLUSTERED。所有表都具有与以前相同的外键和索引。本质上,新格式允许解耦应用程序提供 PkId(作为 external_id),但也允许 PkId 作为 IDENTITY 列值,因此允许通过使用 SCOPE_IDENTITY 和 @@IDENTITY 依赖 IDENTITY 列的现有代码像以前一样工作。

我们遇到的问题是,我们遇到了几个查询,这些查询曾经在可接受的时间内运行,现在完全失败了。这些查询使用的生成的查询计划与以前完全不同。

鉴于新列是 PRIMARY KEY、与以前相同的数据类型和 PERSISTED,我希望查询和查询计划的行为与以前相同。在 SQL Server 将如何生成执行计划方面,COMPUTED PERSISTED INT PkId 的行为是否应该与显式 INT 定义基本相同?您可以看到这种方法还有其他可能的问题吗?

进行此更改的目的应该是允许我们更改表定义,而无需修改现有过程和代码。鉴于这些问题,我认为我们不能采用这种方法。

Sol*_*zky 4

第一的

您可能不需要所有三列:old_id, external_id, new_id。该new_id列是一个IDENTITY,将为每一行生成一个新值,即使您插入到 中也是如此external_id。但是,在old_id和 之间external_id,这些几乎是相互排斥的:要么已经存在一个old_id值,要么在当前概念中该列将只是NULLif using external_idor new_id。由于您不会向已存在的行(即具有值的行old_id)添加新的“外部”id,并且不会有任何新值进入 for old_id,因此可以使用一列出于这两个目的。

因此,删除该external_id列并将其重命名old_id为类似的名称old_or_external_id或其他名称。这不需要对任何东西进行任何真正的改变,但会减少一些复杂性。external_id如果应用程序代码已编写为插入到 中,您最多可能需要调用该列,即使它包含“旧”值external_id

这将新结构简化为:

PkId AS AS COALESCE(old_or_external_id, new_id, -1) PERSISTED NOT NULL,
old_or_external_id INT NULL, -- values from existing record OR passed in from app
new_id INT IDENTITY(2000000, 1) NOT NULL
Run Code Online (Sandbox Code Playgroud)

现在,每行仅添加 8 个字节,而不是 12 个字节(假设您没有使用该SPARSE选项或数据压缩)。而且您不需要更改任何代码、T-SQL 或应用程序代码。

第二

继续沿着这条简化之路,让我们看看还剩下什么:

  • old_or_external_id列要么已经有值,要么将从应用程序中获得新值,要么保留为NULL
  • new_id始终生成一个新值,但仅当列为 时才会使用该old_or_external_idNULL

永远都不需要 和 中的old_or_external_idnew_id。是的,有时两列都会因为new_id是 而具有值IDENTITY,但这些new_id值会被忽略。同样,这两个字段是互斥的。所以现在怎么办?

现在我们可以看看为什么我们external_id首先需要它。考虑到可以使用 插入到IDENTITY列中SET IDENTITY_INSERT {table_name} ON;,您可以完全不进行架构更改,而只需修改您的应用程序代码以将INSERT语句/操作包装在SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;语句中。然后,您需要确定将列重置为什么起始范围IDENTITY(对于新生成的值),因为它需要远高于应用程序代码将插入的值,因为插入更高的值将导致下一个自动生成的值大于当前的 MAX 值。但您始终可以插入低于IDENT_CURRENT值的值。

组合old_or_external_idnew_id列也不会增加在自动生成的值和应用程序生成的值之间遇到重叠值情况的机会,因为拥有 2 甚至 3 列的目的是将它们组合成主键值,这些始终是独特的价值观。

在这种方法中,您只需要:

  • 将表保留为:

    PkId INT IDENTITY(1,1) PRIMARY KEY
    
    Run Code Online (Sandbox Code Playgroud)

    这会向每行添加 0 个字节,而不是 8 个,甚至 12 个。

  • 确定应用程序生成的值的起始范围。这些值将大于每个表中的当前最大值,但小于自动生成值的最小值。
  • 确定自动生成的范围应从什么值开始。当前的 MAX 值之间应该还有很大的空间,并且还有很大的增长空间,因为上限刚刚超过 21.4 亿。然后,您可以通过DBCC CHECKIDENT设置这个新的最小种子值。
  • 将应用程序代码 INSERT 包装在SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;语句中。

第二,B 部分

上面直接提到的方法的一种变体是让应用程序代码插入以 -1 开头并从那里向下的值。这使得这些IDENTITY值成为唯一上涨的。这样做的好处是,您不仅不会使架构复杂化,而且也无需担心遇到重叠的 ID(如果应用程序生成的值遇到新的自动生成的范围)。如果您尚未使用负 ID 值,这只是一个选项(而且人们似乎很少在自动生成的列上使用负值,因此在大多数情况下这应该是可能的)。

在这种方法中,您只需要:

  • 将表保留为:

    PkId INT IDENTITY(1,1) PRIMARY KEY
    
    Run Code Online (Sandbox Code Playgroud)

    这会向每行添加 0 个字节,而不是 8 个,甚至 12 个。

  • 应用程序生成的值的起始范围将为-1.
  • 将应用程序代码 INSERT 包装在SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;语句中。

在这里您仍然需要执行以下操作IDENTITY_INSERT,但是:您不需要添加任何新列,不需要“重新播种”任何IDENTITY列,并且没有未来重叠的风险。

第二,第 3 部分

这种方法的最后一个变体是可能交换列IDENTITY并使用Sequences。采用这种方法的原因是能够让应用程序代码插入以下值:正数、高于自动生成的范围(而不是低于),并且不需要SET IDENTITY_INSERT ON / OFF.

在这种方法中,您只需要:

  • 使用CREATE SEQUENCE创建序列
  • 使用NEXT VALUE FOR函数将该列复制到不具有该属性但具有约束IDENTITY的新列:IDENTITYDEFAULT

    PkId INT PRIMARY KEY CONSTRAINT [DF_TableName_NextID] DEFAULT (NEXT VALUE FOR...)
    
    Run Code Online (Sandbox Code Playgroud)

    这会向每行添加 0 个字节,而不是 8 个,甚至 12 个。

  • 应用程序生成的值的起始范围将远高于您认为自动生成的值将接近的范围。
  • 将应用程序代码 INSERT 包装在SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;语句中。

然而,由于需要使用任一SCOPE_IDENTITY()@@IDENTITY仍然正常运行的代码,目前不能选择切换到序列,因为序列似乎没有与这些功能等效的功能:-(。悲伤!