Thay đổi nhanh cột NVARCHAR (4000) thành NVARCHAR (260)


12

Tôi có một vấn đề về hiệu năng với các khoản trợ cấp bộ nhớ rất lớn xử lý bảng này với một vài NVARCHAR(4000)cột. Điều là những cột này không bao giờ lớn hơn NVARCHAR(260).

Sử dụng

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

dẫn đến SQL Server viết lại toàn bộ bảng (và sử dụng kích thước bảng 2x trong không gian nhật ký), là hàng tỷ hàng, chỉ để thay đổi không có gì, không phải là một tùy chọn. Tăng chiều rộng cột không có vấn đề này, nhưng giảm nó thì có.

Tôi đã thử tạo một ràng buộc CHECK (DATALENGTH([col]) <= 520)hoặc CHECK (LEN([col]) <= 260)SQL Server vẫn quyết định viết lại toàn bộ bảng.

Có cách nào để thay đổi kiểu dữ liệu cột dưới dạng hoạt động chỉ siêu dữ liệu không? Nếu không có chi phí viết lại toàn bộ bảng? Tôi đang sử dụng SQL Server 2017 (14.0.2027.2 và 14.0.3192.2).

Dưới đây là bảng DDL mẫu để sử dụng để tái tạo:

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

Và sau đó chạy ALTER.

Câu trả lời:


15

Có cách nào để thay đổi kiểu dữ liệu cột dưới dạng hoạt động chỉ siêu dữ liệu không?

Tôi không nghĩ vậy, đây là cách sản phẩm hoạt động ngay bây giờ. Có một số cách giải quyết thực sự tuyệt vời cho giới hạn này được đề xuất trong câu trả lời của Joe .

... kết quả là SQL Server viết lại toàn bộ bảng (và sử dụng kích thước bảng 2x trong không gian nhật ký)

Tôi sẽ trả lời riêng hai phần của tuyên bố đó.

Viết lại bảng

Như tôi đã đề cập trước đây, thực sự không có cách nào để tránh điều này. Đó dường như là thực tế của tình huống, ngay cả khi nó không có ý nghĩa hoàn toàn từ quan điểm của chúng tôi là khách hàng.

Nhìn vào DBCC PAGEtrước và sau khi thay đổi cột từ 4000 thành 260 cho thấy tất cả dữ liệu được sao chép trên trang dữ liệu (bảng thử nghiệm của tôi có 'A'260 lần liên tiếp):

Ảnh chụp màn hình phần dữ liệu của trang dbcc trước và sau

Tại thời điểm này, có hai bản sao của cùng một dữ liệu trên trang. Cột "cũ" về cơ bản đã bị xóa (id được thay đổi từ id = 2 thành id = 67108865) và phiên bản "mới" của cột được cập nhật để trỏ đến phần bù mới của dữ liệu trên trang:

Ảnh chụp màn hình các phần siêu dữ liệu cột của trang dbcc trước và sau

Sử dụng kích thước bảng 2x trong không gian nhật ký

Thêm WITH (ONLINE = ON)vào cuối ALTERcâu lệnh làm giảm khoảng một nửa hoạt động ghi nhật ký , vì vậy đây là một cải tiến bạn có thể thực hiện để giảm lượng ghi vào không gian đĩa / đĩa cần thiết.

Tôi đã sử dụng khai thác thử nghiệm này để thử nó:

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

Tôi đã kiểm tra sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)trước và sau khi chạy ALTERcâu lệnh, và đây là sự khác biệt:

Mặc định (Ngoại tuyến) ALTER

  • Tệp dữ liệu ghi / byte được ghi: 34,809 / 2,193,801,216
  • Nhật ký tệp ghi / byte được viết: 40.953 / 1.484.910.080

Trực tuyến ALTER

  • Tệp dữ liệu ghi / byte được ghi: 36.874 / 1.693.745.152 (giảm 22,8%)
  • Tệp nhật ký ghi / byte được ghi: 24.680 / 866.166.272 (giảm 41%)

Như bạn có thể thấy, có một sự sụt giảm nhẹ trong việc ghi tệp dữ liệu và sự sụt giảm lớn trong tệp nhật ký ghi.


15

Tôi không biết cách trực tiếp thực hiện những gì bạn đang tìm kiếm ở đây. Lưu ý rằng trình tối ưu hóa truy vấn không đủ thông minh tại thời điểm này để tạo ra các ràng buộc cho các tính toán cấp bộ nhớ, do đó, ràng buộc này sẽ không giúp được gì. Một vài phương pháp tránh viết lại dữ liệu của bảng:

  1. CAST cột dưới dạng NVARCHAR (260) trong tất cả các mã sử dụng nó. Trình tối ưu hóa truy vấn sẽ tính toán cấp bộ nhớ bằng cách sử dụng kiểu dữ liệu được truyền thay vì kiểu dữ liệu thô.
  2. Đổi tên bảng và tạo một khung nhìn thay thế diễn viên. Điều này thực hiện tương tự như tùy chọn 1 nhưng có thể giới hạn số lượng mã bạn cần cập nhật.
  3. Tạo một cột được tính toán không tồn tại với loại dữ liệu phù hợp và có tất cả các truy vấn của bạn được chọn từ cột đó thay vì cột ban đầu.
  4. Đổi tên cột hiện có và thêm cột được tính toán với tên gốc. Sau đó điều chỉnh tất cả các truy vấn của bạn thực hiện cập nhật hoặc chèn vào cột ban đầu để sử dụng tên cột mới thay thế.

2

Tôi đã ở trong một tình huống tương tự nhiều lần.

Các bước:

Thêm một col mới có chiều rộng mong muốn

Sử dụng một con trỏ, với vài nghìn lần lặp (có thể là mười hoặc hai mươi nghìn) cho mỗi lần cam kết sao chép dữ liệu từ cột cũ sang cột mới

Thả cột cũ

Đổi tên cột mới thành tên của cột cũ

Tada!


3
Điều gì xảy ra nếu một số hồ sơ bạn đã sao chép cuối cùng được cập nhật hoặc xóa?
George.Palacios

1
Thật dễ dàng để làm một trận chung kết update table set new_col = old_col where new_col <> old_col;trước khi rơi old_col.
Colin 't Hart

1
@ Colin't Cách tiếp cận đó sẽ không hiệu quả với hàng triệu hàng ... giao dịch trở nên rất lớn và nó bị chặn ....
Jonesome phục hồi lại

@samsmith Trước tiên, bạn làm những gì bạn mô tả ở trên. Sau đó, trước khi bỏ cột ban đầu, nếu có bất kỳ cập nhật nào cho dữ liệu gốc trong thời gian đó, hãy chạy câu lệnh cập nhật đó. Nó chỉ ảnh hưởng đến một vài hàng đã được sửa đổi. Hay tôi đang thiếu một cái gì đó?
Colin 't Hart

Để bao gồm các hàng được cập nhật trong quá trình, cố gắng tránh quét toàn bộ mà where new_col <> old_colkhông có điều khoản lọc nào khác sẽ dẫn đến, bạn có thể thêm một trình kích hoạt để thực hiện các thay đổi này khi chúng xảy ra và xóa phần cuối của quy trình. Vẫn là một hiệu suất tiềm năng, nhưng nhiều lượng nhỏ trong suốt quá trình thay vì một lần truy cập lớn ở cuối, có lẽ (tùy thuộc vào mẫu cập nhật ứng dụng của bạn cho bảng) cộng lại ít hơn rất nhiều so với một lần nhấn lớn .
David Spillett

1

Vâng, có một sự thay thế tùy thuộc vào không gian có sẵn trong cơ sở dữ liệu của bạn.

  1. Tạo một bản sao chính xác của bảng của bạn (ví dụ new_table), ngoại trừ cột mà bạn sẽ rút ngắn từ NVARCHAR(4000)đến 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. Trong cửa sổ bảo trì, sao chép dữ liệu từ bảng "bị hỏng" ( table) sang bảng "cố định" ( new_table) một cách đơn giản INSERT ... 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. Đổi tên bảng "bị hỏng" tablethành một cái khác:

    EXEC sp_rename 'table', 'old_table';  
  4. Đổi tên bảng "đã sửa" new_tablethành table:

    EXEC sp_rename 'new_table', 'table';  
  5. Nếu mọi thứ đều ổn, hãy bỏ bảng đổi tên "bị hỏng":

     DROP TABLE [old_table]
     GO

Có bạn đi.

Trả lời câu hỏi của bạn

Có cách nào để thay đổi kiểu dữ liệu cột dưới dạng hoạt động chỉ siêu dữ liệu không?

Không. Hiện tại không thể

Nếu không có chi phí viết lại toàn bộ bảng?

Không.
( Xem giải pháp của tôi và những người khác. )


Kết quả "chèn vào lựa chọn" của bạn sẽ dẫn đến một bảng lớn (hàng triệu hoặc hàng tỷ hàng) trong giao dịch ENORMOUS, có thể khiến DB tạm dừng trong hàng chục hoặc hàng trăm phút. (Cũng như làm cho ldf khổng lồ và có thể phá vỡ vận chuyển gỗ, nếu được sử dụng)
Jonesome Rebstate Monica
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.