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

Nic*_*ley 13 sql-server alter-table sql-server-2017

我有一个性能问题,非常大的内存授予处理这个包含几NVARCHAR(4000)列的表。事情是这些列永远不会大于NVARCHAR(260).

使用

ALTER TABLE [table] ALTER COLUMN [col] NVARCHAR(260) NULL
Run Code Online (Sandbox Code Playgroud)

导致 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)
);
Run Code Online (Sandbox Code Playgroud)

然后运行ALTER.

Joe*_*ish 16

我不知道有什么方法可以直接完成您在这里寻找的内容。请注意,此时查询优化器不够智能,无法将内存授予计算的约束因素考虑在内,因此无论如何该约束都无济于事。一些避免重写表数据的方法:

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


Jos*_*ell 16

有没有办法将列数据类型更改为仅限元数据的操作?

我不这么认为,这就是产品现在的工作方式。对于Joe's answer 中提出的这个限制,有一些非常好的解决方法。

...导致 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
Run Code Online (Sandbox Code Playgroud)

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 %)

如您所见,数据文件写入略有下降,日志文件写入大幅下降。