Trước tiên, hãy xem xét nhanh cách SQL Server tạo thứ tự các cột mặc định khi bạn thực hiện SELECT *truy vấn. Có thể có một vài trường hợp cạnh nhưng tôi tin rằng các cột được trả về theo thứ tự mà chúng được tạo. Nếu một cột bị loại bỏ, ID thứ tự liên kết với cột đó sẽ không được sử dụng và không thể được sử dụng bởi một cột mới.
Xem xét bảng mẫu này:
CREATE TABLE dbo.SEE_COLUMN_ORDER (
COL1 INT,
COL2 INT,
COL3 INT,
COL4 INT
);
Chúng ta có thể thấy cột_id sử dụng truy vấn này đối với sys.columns :
SELECT name, column_id
FROM sys.columns
WHERE object_id = OBJECT_ID('SEE_COLUMN_ORDER');
Kết quả ban đầu:
╔══════╦═══════════╗
║ name ║ column_id ║
╠══════╬═══════════╣
║ COL1 ║ 1 ║
║ COL2 ║ 2 ║
║ COL3 ║ 3 ║
║ COL4 ║ 4 ║
╚══════╩═══════════╝
Bây giờ thả một cột:
ALTER TABLE dbo.SEE_COLUMN_ORDER DROP COLUMN COL3;
Kết quả mới:
╔══════╦═══════════╗
║ name ║ column_id ║
╠══════╬═══════════╣
║ COL1 ║ 1 ║
║ COL2 ║ 2 ║
║ COL4 ║ 4 ║
╚══════╩═══════════╝
Bây giờ thêm một cột:
ALTER TABLE dbo.SEE_COLUMN_ORDER ADD COL3 INT;
Kết quả mới:
╔══════╦═══════════╗
║ name ║ column_id ║
╠══════╬═══════════╣
║ COL1 ║ 1 ║
║ COL2 ║ 2 ║
║ COL4 ║ 4 ║
║ COL3 ║ 5 ║
╚══════╩═══════════╝
Không có cột nào có column_idsố 3. Theo như tôi có thể nói thì không có cách nào được biết hoặc được hỗ trợ để COL3có một column_idsố 3. Nếu bạn muốn cột đó được hiển thị thứ ba trong một SELECT *truy vấn, bạn có thể xác định chế độ xem đối với bảng tên và một lược đồ khác hoặc bạn có thể thả và tạo lại toàn bộ bảng với thứ tự cột mà bạn muốn.
Việc tạo lại toàn bộ bảng nghe có vẻ như hoạt động chậm nhưng đôi khi có thể nhanh hơn việc cập nhật tất cả các hàng cho một cột. Nó sẽ phụ thuộc vào hệ thống của bạn, cấu trúc bảng của bạn và dữ liệu trong bảng của bạn. Một kịch bản trong đó việc thả và tạo có thể nhanh hơn là nếu nhật ký giao dịch của bạn ghi là nút cổ chai. Với mô hình khôi phục đơn giản, bạn có thể tạo bảng mới với ghi nhật ký tối thiểu sẽ ghi rất ít dữ liệu vào nhật ký giao dịch so với UPDATE. Một kịch bản khác trong đó việc thả và tạo lại có thể nhanh hơn là nếu điều đó UPDATEsẽ dẫn đến nhiều sự chia tách trang. Có thể xây dựng một bảng và một bảng UPDATEchia mỗi trang dữ liệu thành hai (một UPDATEgiá trị thành một cột làm cho nóNULLTôi sẽ không làm điều này theo như tôi biết). Theo nguyên tắc chung, cùng một lượng dữ liệu được lưu vào nhật ký bất kể mô hình khôi phục trừ khi hoạt động có thể được ghi lại tối thiểu. UPDATEkhông bao giờ được ghi lại tối thiểu để chuyển sang đơn giản sẽ không làm giảm yêu cầu nhật ký giao dịch cho hoạt động đó.
Để giải quyết phần câu hỏi liên quan đến hiệu suất, một điều quan trọng cần lưu ý là việc thêm và thả một cột vào bảng hiện có là các hoạt động được tối ưu hóa có chi phí cố định không chia tỷ lệ với lượng dữ liệu trong bảng. Để thấy điều này trong thực tế, tôi sẽ xem xét có bao nhiêu dữ liệu được ghi vào nhật ký giao dịch cho các hoạt động (thả và thêm các cột có thể được khôi phục trong một giao dịch). Tôi đang thử nghiệm với SQL Server 2016.
Đây là dữ liệu mẫu:
DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_2;
CREATE TABLE dbo.X_COLUMN_WIPE_2 (
ID INT NOT NULL IDENTITY (1, 1),
COL_TO_WIPE VARCHAR(1) NULL,
FILLER VARCHAR(100) NULL,
PRIMARY KEY (ID)
);
-- 2536 rows
INSERT INTO dbo.X_COLUMN_WIPE_2 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1;
Mỗi sys.dm_tran_database_transilities , UPDATEghi 324752 byte nhật ký vào nhật ký giao dịch:
BEGIN TRANSACTION
UPDATE dbo.X_COLUMN_WIPE_2 SET COL_TO_WIPE = NULL;
ROLLBACK;
Thả và thêm một cột chỉ ghi các byte nhật ký 1992 vào nhật ký:
BEGIN TRANSACTION
ALTER TABLE dbo.X_COLUMN_WIPE_2 DROP COLUMN COL_TO_WIPE;
ALTER TABLE dbo.X_COLUMN_WIPE_2 ADD NEW_COLUMN VARCHAR(1) NULL;
ROLLBACK;
Bây giờ để kiểm tra với nhiều dữ liệu hơn:
DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_3;
CREATE TABLE dbo.X_COLUMN_WIPE_3 (
ID INT NOT NULL IDENTITY (1, 1),
COL_TO_WIPE VARCHAR(1) NULL,
FILLER VARCHAR(100) NULL,
PRIMARY KEY (ID)
);
-- 6431296 rows
INSERT INTO dbo.X_COLUMN_WIPE_3 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
Hiện UPDATEtại cùng ghi 721979808 byte vào nhật ký giao dịch nhưng bỏ và tạo cột vẫn chỉ ghi 1992 byte.
Có thể đào sâu vào bên trong lý do tại sao điều này xảy ra bằng cách sử dụng TRANG DBCC không có giấy tờ . Đây là một ví dụ từ khi tôi chạy mã trên hệ thống của mình (sao chép và dán mã sẽ không hoạt động vì số trang sẽ khác nhau):
DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_4;
CREATE TABLE dbo.X_COLUMN_WIPE_4 (
ID INT NOT NULL IDENTITY (1, 1),
COL_TO_WIPE VARCHAR(1) NULL,
FILLER VARCHAR(100) NULL,
PRIMARY KEY (ID)
);
INSERT INTO dbo.X_COLUMN_WIPE_4 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1;
-- first first data page
DBCC IND('SE_DB',X_COLUMN_WIPE_4,-1)
-- view first data page
DBCC TRACEON(3604)
DBCC PAGE('SE_DB',1,1192232,3);
BEGIN TRANSACTION
UPDATE dbo.X_COLUMN_WIPE_4 SET COL_TO_WIPE = NULL;
DBCC PAGE('SE_DB',1,1192232,3);
ROLLBACK;
Như Paul White sẽ nói tôi khá nghiệp dư khi nói đến những thứ này, nhưng dù sao tôi cũng sẽ đưa ra lời giải thích của mình. Đây là một khác biệt của những gì trong trang cho bản ghi đầu tiên trong bảng trước và sau khi cập nhật:

Tôi nhấn mạnh những gì tôi nghi ngờ là các trang quan trọng màu đỏ. Lưu ý rằng nhiều độ dài vật lý đã thay đổi và giá trị "A" COL_TO_WIPEkhông còn xuất hiện trong dữ liệu hàng. Có vẻ như phần UPDATElớn dữ liệu được lưu trữ trong trang đã thay đổi.
Đây là một khác biệt giữa bảng gốc và sau khi COL_TO_WIPEcột bị hủy:

Số lượng khác biệt nhỏ hơn nhiều so với trước đây. Không có dữ liệu hàng nào được thay đổi. Tôi tin rằng chỉ có một hoạt động siêu dữ liệu được thực hiện trên bảng bên ngoài các trang của bảng, vì vậy tất cả các thay đổi được hiển thị DBCC PAGEở đây là logic trái ngược với vật lý.
Đây là một khác biệt giữa bảng gốc và sau khi cột bị loại bỏ và một cột mới được thêm vào:

Như trước đây không thực sự có bất kỳ sự khác biệt về thể chất. Các dữ liệu thực tế trông giống hệt nhau. Chỉ có các định nghĩa cột với độ dài vật lý là 0.
Vì vấn đề ở đây dường như liên quan đến cách ngày được lưu trữ theo từng hàng trong các trang, có lẽ kết quả sẽ khác nhau đối với các bảng được tạo dưới dạng các chỉ mục của cột được phân cụm được lưu trữ ở định dạng cột. Rốt cuộc, thật hợp lý khi nghĩ rằng việc cập nhật một giá trị cột chỉ nên ảnh hưởng đến một cột thay vì tất cả dữ liệu trong bảng. Thật không may cho trường hợp này, việc triển khai UPDATEchống lại CCI hiện tại là xóa và chèn logic. Vì vậy, nếu bạn cập nhật tất cả các hàng cho một cột NULL, SQL Server sẽ đánh dấu tất cả các hàng hiện có trong bảng là đã xóa một cách hợp lý và sẽ tạo các nhóm hàng mới cho tất cả các hàng.
Tóm lại, ngoài việc tạo lại toàn bộ bảng bạn đã đề cập đến tất cả các cách "thông minh" trong câu hỏi mà tôi biết để giải quyết vấn đề của mình. Rất khó có cách "cập nhật" tức thì một cột NULLở giữa bảng mà không phá vỡ thứ tự cột trước đó trong bảng.