将NVARCHAR(4000)列快速更改为NVARCHAR(260)


12

我在使用非常大的内存授予时遇到了一个性能问题,需要处理两NVARCHAR(4000)列该表。问题是这些列永远不会大于NVARCHAR(260)

使用

ALTER TABLE [table] ALTER COLUMN [col] NVARCHAR(260) NULL

导致SQL Server重写整个表(并在日志空间中使用2倍的表大小),这是数十亿行,只能更改任何内容,这是不可行的。增大列宽不会出现此问题,但是减小它确实会出现此问题。

我尝试创建约束,CHECK (DATALENGTH([col]) <= 520)或者CHECK (LEN([col]) <= 260)SQL Server仍然决定重新编写整个表。

有什么方法可以将列数据类型更改为仅元数据操作?无需重写整个表?我正在使用SQL Server 2017(14.0.2027.2和14.0.3192.2)。

这是用于重现的示例DDL表:

CREATE TABLE [table](
    id INT IDENTITY(1,1) NOT NULL,
    [col] NVARCHAR(4000) NULL,
    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

然后运行ALTER

Answers:


16

我不知道一种直接完成您在这里寻找的方法的方法。请注意,查询优化器目前还不够智能,无法考虑内存授权计算的约束,因此该约束无论如何也无济于事。避免重写表数据的几种方法:

  1. 在使用该列的所有代码中将其列为NVARCHAR(260)。查询优化器将使用强制转换的数据类型而不是原始数据类型来计算内存授权。
  2. 重命名该表并创建一个执行强制转换的视图。这可以实现与选项1相同的功能,但是可能会限制您需要更新的代码量。
  3. 创建一个具有正确数据类型的非持久计算列,并从该列中选择所有查询,而不是原始查询。
  4. 重命名现有列,并使用原始名称添加计算列。然后调整所有查询以进行更新或插入到原始列,以改用新的列名。

15

有什么方法可以将列数据类型更改为仅元数据操作?

我不这么认为,这就是产品现在的工作方式。在Joe的答案中提出了一些非常好的解决此限制的方法。

...导致SQL Server重写整个表(并在日志空间中使用2x表大小)

我将分别回答该声明的两个部分。

重写表

如前所述,实际上没有任何方法可以避免这种情况。即使从我们作为客户的角度来看,这并不完全有意义,但这似乎是现实。

查看DBCC PAGE将列从4000更改为260之前和之后,发现所有数据都在数据页面上重复(我的测试表'A'在该行中有260次):

之前和之后dbcc页面的数据部分的屏幕快照

此时,页面上有两个完全相同的数据副本。本质上删除了“旧”列(将ID从ID = 2更改为ID = 67108865),并且更新了该列的“新”版本以指向页面上数据的新偏移量:

dbcc页面之前和之后的列元数据部分的屏幕快照

在日志空间中使用2x表大小

将语句添加WITH (ONLINE = ON)ALTER语句末尾可使日志记录活动减少一半左右,因此这是您可以减少对磁盘/磁盘空间的写入量的一项改进。

我使用此测试工具进行了尝试:

USE [master];
GO
DROP DATABASE IF EXISTS [248749];
GO
CREATE DATABASE [248749] 
ON PRIMARY 
(
    NAME = N'248749', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749.mdf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
)
LOG ON 
(
    NAME = N'248749_log', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749_log.ldf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
);
GO
USE [248749];
GO

CREATE TABLE dbo.[table]
(
    id int IDENTITY(1,1) NOT NULL,
    [col] nvarchar (4000) NULL,

    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

INSERT INTO dbo.[table]
SELECT TOP (1000000)
    REPLICATE(N'A', 260)
FROM master.dbo.spt_values v1
    CROSS JOIN master.dbo.spt_values v2
    CROSS JOIN master.dbo.spt_values v3;
GO

sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)在运行该ALTER语句之前和之后进行了检查,这是不同之处:

默认(离线) ALTER

  • 写入数据文件/写入字节:34,809 / 2,193,801,216
  • 日志文件写入数/写入的字节数:40,953 / 1,484,910,080

线上 ALTER

  • 写入数据文件/写入字节:36,874 / 1,693,745,152(下降22.8%)
  • 日志文件写入数/写入字节数:24,680 / 866,166,272(下降41%)

如您所见,数据文件写入量略有下降,而日志文件写入量则有较大下降。


2

我曾多次遇到过类似情况。

脚步 :

添加所需宽度的新列

使用游标,每个提交进行几千次迭代(也许是一万或两万次),将数据从旧列复制到新列

删除旧列

重命名新列为旧列的名称

多田!


3
如果您已经复制的某些记录最终被更新或删除了怎么办?
George.Palacios

1
update table set new_col = old_col where new_col <> old_col;在放下决赛之前很容易old_col
Colin't Hart

1
@ Colin'tHart这种方法与数百万行的将无法正常工作......交易变得巨大,它阻止....
Jonesome恢复莫妮卡

@samsmith首先,您要执行上面的描述。然后,在删除原始列之前,如果同时对原始数据进行了任何更新,请运行该更新语句。它只会影响已修改的几行。还是我错过了什么?
Colin't Hart

为了覆盖在流程中更新的行,为避免完全扫描where new_col <> old_col而没有其他过滤子句,您可以添加触发器以将这些更改保留下来,并在流程结束时将其删除。仍然是潜在的性能损失,但是在整个过程中会产生很多影响,而不是在结束时产生巨大的损失,这(取决于应用程序对表的更新模式)的总数可能远远少于一个巨大的损失。
David Spillett

1

嗯,还有一种选择,具体取决于数据库中的可用空间。

  1. 创建表的精确副本(例如new_table),但要缩短NVARCHAR(4000)到的列除外NVARCHAR(260)

    CREATE TABLE [new_table](
        id INT IDENTITY(1,1) NOT NULL,
        [col] NVARCHAR(260) NULL,
        CONSTRAINT [PK_test_new] PRIMARY KEY CLUSTERED (id ASC)
    );
  2. 在维护窗口中,可以使用以下简单方法将数据从“损坏”表(table)复制到“固定”表(new_tableINSERT ... INTO ... SELECT ....

    SET IDENTITY_INSERT [new_table] ON
    GO
    INSERT id, col INTO [new_table] SELECT id, col from [table]
    GO
    SET IDENTITY_INSERT [new_table] OFF
    GO
  3. 将“残破”表重命名为table其他名称:

    EXEC sp_rename 'table', 'old_table';  
  4. 将“固定”表重命名new_tabletable

    EXEC sp_rename 'new_table', 'table';  
  5. 如果一切正常,请删除“损坏的”重命名表:

     DROP TABLE [old_table]
     GO

你去。

回答你的问题

有什么方法可以将列数据类型更改为仅元数据操作?

否。目前无法

无需重写整个表?

否。
请参阅我的解决方案及其他。


您的“插入选择项”将导致在一个巨大的表(数百万或数十亿行)中发生巨大的事务,这可能会使数据库暂停数十或数百分钟。(以及使ldf变得庞大,并可能打破原木运输,如果使用的话)
Jonesome Reinstate Monica
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.