批量更改表添加列,但使用 NEWSEQUENTIALID()

Reb*_*cca 7 sql-server sql-server-2008-r2

我有一个很大的表,但不是很大(在老化的硬件上少于 200 万行),并且当向现有表添加不可空列时,我通常遵循此处列出的结构,以避免脚本超时的问题我们的数据库迁移在部署上运行(仅供参考 - 这不是全文索引问题)。

所以,总而言之,我:

  • 更改表并将列添加为 NULL 并且不添加默认约束
  • 批量回填柱
  • 更改表并将列更改为 NOT NULL 并添加默认约束

但是,在以下情况下,我想添加一个新的 UNIQUEIDENTIFER 列并用 NEWSEQUENTIALID() 而不是 NEWID() 值填充它。

如果不批量运行,我的脚本将如下所示:

IF NOT EXISTS (
    SELECT NULL FROM INFORMATION_SCHEMA.COLUMNS 
    WHERE TABLE_NAME = 'Invoice' 
    AND COLUMN_NAME = 'InternalId')
BEGIN
    ALTER TABLE Invoice 
        ADD InternalId UNIQUEIDENTIFIER NOT NULL 
        CONSTRAINT [DF_Invoice_InternalId] DEFAULT (NEWSEQUENTIALID())
END
GO
Run Code Online (Sandbox Code Playgroud)

但是,如果我将其分成批次,并尝试使用以下内容填充可为空的 InternalId:

IF NOT EXISTS (
    SELECT NULL FROM INFORMATION_SCHEMA.COLUMNS 
    WHERE TABLE_NAME = 'Invoice' 
    AND COLUMN_NAME = 'InternalId')
BEGIN
    ALTER TABLE Invoice 
        ADD InternalId UNIQUEIDENTIFIER NULL
END

DECLARE @MaxId INT, @LoopStart INT, @LoopEnd INT, @LoopSize INT = 50000
SELECT @MaxId = MAX(InvoiceId) FROM Invoice
SELECT @LoopStart = MIN(InvoiceId) FROM Invoice
SET @LoopEnd = @LoopStart + @LoopSize

PRINT 'Updating InternalIds to a new GUID'
WHILE @LoopStart <= @MaxId
BEGIN
    -- update internal id
    UPDATE I 
    SET InternalId = NEWSEQUENTIALID()
    FROM Invoice I
    WHERE I.InvoiceId BETWEEN @LoopStart AND @LoopEnd

    SET @LoopStart = @LoopEnd + 1
    SET @LoopEnd = @LoopEnd + @LoopSize
END

IF EXISTS (
    SELECT NULL FROM INFORMATION_SCHEMA.COLUMNS 
    WHERE TABLE_NAME = 'Invoice' 
    AND COLUMN_NAME = 'InternalId' 
    AND IS_NULLABLE = 'YES')
BEGIN
    ALTER TABLE Invoice 
        ALTER COLUMN InternalId UNIQUEIDENTIFIER NOT NULL
END

IF NOT EXISTS (SELECT NULL FROM sys.objects WHERE name = 'DF_Invoice_InternalId')
BEGIN
    ALTER TABLE Invoice
        ADD CONSTRAINT [DF_Invoice_InternalId]  
        DEFAULT ((NEWSEQUENTIALID())) FOR [InternalId]
END
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

消息 302,级别 16,状态 0,第 40 行 newsequentialid() 内置函数只能在 CREATE TABLE 或 ALTER TABLE 语句中“uniqueidentifier”类型列的 DEFAULT 表达式中使用。它不能与其他运算符组合形成复杂的标量表达式。

关于如何解决这个问题有什么建议吗?还是我想太多了?

进行此更改的原因是在 API 中向外部公开顺序 ID(InternalId或可以调用),作为当前顺序数字 Id (InvoiceId) 的替代。PublicId数字 Id(主键)应该保留在内部,因为它公开了连续且可猜测的内部值。Sequential GUID 仍然是连续的,但也不那么容易猜到。为了说明这一点,我正在做类似的事情,但 @First 是通过 API 调用提供的。它用于使用水印过程轮询和处理新发票。

CREATE TABLE #Test (
    Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    DateCreated DATETIME NOT NULL DEFAULT(GETDATE()),
    Code NVARCHAR(50) NOT NULL
)

DECLARE @Id INT
DECLARE @NO_OF_CHARS INT = 10
SET @Id = 1

WHILE @Id <= 12000
BEGIN 
    
   INSERT INTO #Test (Code) VALUES (SUBSTRING (REPLACE(CONVERT(VARCHAR(40), NEWID()), '-',''), 1, @NO_OF_CHARS))
   SET @Id = @Id + 1
END

ALTER TABLE #Test 
    ADD InternalId UNIQUEIDENTIFIER NOT NULL DEFAULT(NEWSEQUENTIALID())

DECLARE @First UNIQUEIDENTIFIER
SELECT * FROM #Test
SELECT @First = InternalId FROM #Test WHERE Id = 1
SELECT * FROM #Test WHERE InternalID > @First

DROP TABLE #Test
Run Code Online (Sandbox Code Playgroud)

Bre*_*rey 0

请注意,NEWSEQUENTIALID 不能保证在表存在的生命周期内 100% 顺序 GUID。每次服务器重新启动后,NEWSEQUENTIALID 可能会恢复为较低的值。请查看有关此内容的Microsoft 文档。如果这将成为一个问题,您可能需要重新评估其在初始更新中的使用情况。

据我了解,NEWSEQUENTIALID 的主要目的是减少使用 GUID 作为聚集键时的痛苦,以避免跨数据页和页面拆分的随机插入