ALTER TABLE phiên bản DROP COLUMN có thực sự là một hoạt động siêu dữ liệu không?


11

Tôi đã tìm thấy một số nguồn trạng thái ALTER TABLE ... DROP COLUMN là một hoạt động chỉ có siêu dữ liệu.

Nguồn

Làm sao có thể? Có phải dữ liệu trong DROP COLUMN không cần phải được xóa khỏi các chỉ mục không được nhóm bên dưới và chỉ mục / heap cụm?

Ngoài ra, tại sao Microsoft Docs lại ám chỉ rằng đó là một hoạt động được ghi lại đầy đủ?

Các sửa đổi được thực hiện cho bảng được ghi lại và có thể phục hồi hoàn toàn. Các thay đổi ảnh hưởng đến tất cả các hàng trong các bảng lớn, chẳng hạn như bỏ một cột hoặc, trên một số phiên bản của SQL Server, việc thêm cột KHÔNG NULL với giá trị mặc định, có thể mất nhiều thời gian để hoàn thành và tạo nhiều bản ghi nhật ký . Chạy các câu lệnh ALTER TABLE này với cùng một cách chăm sóc như bất kỳ câu lệnh INSERT, UPDATE hoặc DELETE nào ảnh hưởng đến nhiều hàng.

Như một câu hỏi phụ: làm thế nào để động cơ theo dõi các cột bị rơi nếu dữ liệu không bị xóa khỏi các trang bên dưới?


2
Chà, tôi nghĩ rằng ngôn ngữ đã tồn tại qua nhiều phiên bản của sản phẩm và nhiều lần lặp lại của tài liệu. Theo thời gian, ngày càng nhiều hoạt động liên quan đến các cột đã trở thành thay đổi trực tuyến / siêu dữ liệu. Bây giờ có lẽ là một ví dụ cụ thể tồi tệ, nhưng mục đích của câu chỉ đơn giản là để cảnh báo bạn rằng, nói chung, một số thao tác thay đổi có thể là các hoạt động kích thước dữ liệu trong các tình huống nhất định , thay vì liệt kê ra từng kịch bản cụ thể.
Aaron Bertrand

Câu trả lời:


14

Có một số trường hợp nhất định trong đó việc thả một cột có thể là một hoạt động chỉ có siêu dữ liệu. Các định nghĩa cột cho bất kỳ bảng đã cho nào không được bao gồm trong mỗi và mỗi trang nơi các hàng được lưu trữ, các định nghĩa cột chỉ được lưu trữ trong siêu dữ liệu cơ sở dữ liệu, bao gồm sys.sysrowsets, sys.sysrscols, v.v.

Khi thả một cột không được tham chiếu bởi bất kỳ đối tượng nào khác, công cụ lưu trữ chỉ cần đánh dấu định nghĩa cột là không còn nữa bằng cách xóa các chi tiết thích hợp khỏi các bảng hệ thống khác nhau. Hành động xóa dữ liệu meta làm mất hiệu lực bộ đệm thủ tục, yêu cầu biên dịch lại bất cứ khi nào một truy vấn sau đó tham chiếu bảng đó. Vì biên dịch lại chỉ trả về các cột hiện tồn tại trong bảng, nên các chi tiết cột cho cột bị rớt thậm chí không bao giờ được yêu cầu; công cụ lưu trữ bỏ qua các byte được lưu trữ trong mỗi trang cho cột đó, như thể cột không còn tồn tại.

Khi một hoạt động DML tiếp theo xảy ra đối với bảng, các trang bị ảnh hưởng sẽ được viết lại mà không có dữ liệu cho cột bị bỏ. Nếu bạn xây dựng lại một chỉ mục được nhóm hoặc một đống, tất cả các byte cho cột bị rơi tự nhiên không được ghi lại vào trang trên đĩa. Điều này có hiệu quả lan truyền tải trọng của việc thả cột theo thời gian, làm cho nó ít được chú ý hơn.

Có những trường hợp bạn không thể thả một cột, chẳng hạn như khi cột được bao gồm trong một chỉ mục hoặc khi bạn đã tạo thủ công một đối tượng thống kê cho cột. Tôi đã viết một bài đăng trên blog cho thấy lỗi được trình bày khi cố gắng thay đổi một cột bằng một đối tượng thống kê được tạo thủ công. Các ngữ nghĩa tương tự được áp dụng khi thả một cột - nếu cột được tham chiếu bởi bất kỳ đối tượng nào khác, thì nó không thể bị loại bỏ. Đối tượng tham chiếu phải được thay đổi trước, sau đó cột có thể được loại bỏ.

Điều này khá dễ dàng để hiển thị bằng cách xem nội dung của nhật ký giao dịch sau khi thả một cột. Mã dưới đây tạo một bảng với một cột char dài 8.000. Nó thêm một hàng, sau đó thả nó và hiển thị nội dung của nhật ký giao dịch áp dụng cho hoạt động thả. Các bản ghi nhật ký hiển thị các sửa đổi cho các bảng hệ thống khác nhau nơi các định nghĩa bảng và cột được lưu trữ. Nếu dữ liệu cột thực sự bị xóa khỏi các trang được phân bổ vào bảng, bạn sẽ thấy các bản ghi nhật ký ghi lại dữ liệu trang thực tế; không có hồ sơ như vậy.

DROP TABLE IF EXISTS dbo.DropColumnTest;
GO
CREATE TABLE dbo.DropColumnTest
(
    rid int NOT NULL
        CONSTRAINT DropColumnTest_pkc
        PRIMARY KEY CLUSTERED
    , someCol varchar(8000) NOT NULL
);

INSERT INTO dbo.DropColumnTest (rid, someCol)
SELECT 1, REPLICATE('Z', 8000);
GO

DECLARE @startLSN nvarchar(25);

SELECT TOP(1) @startLSN = dl.[Current LSN]
FROM sys.fn_dblog(NULL, NULL) dl
ORDER BY dl.[Current LSN] DESC;

DECLARE @a int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),      LEFT(@startLSN, 8), 0), 1)
      , @b int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), SUBSTRING(@startLSN, 10, 8), 0), 1)
      , @c int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),     RIGHT(@startLSN, 4), 0), 1);

SELECT @startLSN = CONVERT(varchar(8), @a, 1) 
    + ':' + CONVERT(varchar(8), @b, 1) 
    + ':' + CONVERT(varchar(8), @c, 1)

ALTER TABLE dbo.DropColumnTest DROP COLUMN someCol;

SELECT *
FROM sys.fn_dblog(@startLSN, NULL)


--modify an existing data row 
SELECT TOP(1) @startLSN = dl.[Current LSN]
FROM sys.fn_dblog(NULL, NULL) dl
ORDER BY dl.[Current LSN] DESC;

SET @a = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),      LEFT(@startLSN, 8), 0), 1);
SET @b = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), SUBSTRING(@startLSN, 10, 8), 0), 1);
SET @c = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),     RIGHT(@startLSN, 4), 0), 1);

SELECT @startLSN = CONVERT(varchar(8), @a, 1) 
    + ':' + CONVERT(varchar(8), @b, 1) 
    + ':' + CONVERT(varchar(8), @c, 1)

UPDATE dbo.DropColumnTest SET rid = 2;

SELECT *
FROM sys.fn_dblog(@startLSN, NULL)

(Đầu ra quá lớn để hiển thị ở đây và dbfiddle.uk sẽ không cho phép tôi truy cập fn_dblog)

Tập hợp đầu ra đầu tiên hiển thị nhật ký là kết quả của câu lệnh DDL thả cột. Bộ đầu ra thứ hai hiển thị nhật ký sau khi chạy câu lệnh DML nơi chúng tôi cập nhật ridcột. Trong tập kết quả thứ hai, chúng ta thấy các bản ghi nhật ký chỉ ra việc xóa đối với dbo.DropColumnTest, sau đó là chèn vào dbo.DropColumnTest. Mỗi Độ dài bản ghi nhật ký là 8116, cho biết trang thực tế đã được cập nhật.

Như bạn có thể thấy từ đầu ra của fn_dbloglệnh trong thử nghiệm ở trên, toàn bộ hoạt động được ghi lại đầy đủ. Điều này đi để phục hồi đơn giản, cũng như phục hồi đầy đủ. Thuật ngữ "đăng nhập đầy đủ" có thể bị hiểu sai vì sửa đổi dữ liệu không được ghi lại. Đây không phải là những gì xảy ra - sửa đổi được ghi lại, và có thể được khôi phục hoàn toàn. Nhật ký được đơn giản chỉ ghi lại các trang đã được chạm vào, và vì không ai trong số của bảng dữ liệu trang đã được đăng nhập bởi các hoạt động DDL, cả hai DROP COLUMN, và bất kỳ rollback có thể xảy ra sẽ xảy ra rất nhanh chóng, không phụ thuộc vào kích thước của bảng.

Đối với khoa học , đoạn mã sau sẽ kết xuất các trang dữ liệu cho bảng được bao gồm trong đoạn mã trên, sử dụng DBCC PAGEkiểu "3". Kiểu "3" cho biết chúng tôi muốn tiêu đề trang cộng với giải thích chi tiết theo từng hàng . Mã sử ​​dụng một con trỏ để hiển thị chi tiết cho mọi trang trong bảng, vì vậy bạn có thể muốn đảm bảo rằng bạn không chạy mã này trên một bảng lớn.

DBCC TRACEON(3604); --directs out from DBCC commands to the console, instead of the error log
DECLARE @dbid int = DB_ID();
DECLARE @fileid int;
DECLARE @pageid int;
DECLARE cur CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR
SELECT dpa.allocated_page_file_id
    , dpa.allocated_page_page_id
FROM sys.schemas s  
    INNER JOIN sys.objects o ON o.schema_id = s.schema_id
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), o.object_id, NULL, NULL, 'DETAILED') dpa
WHERE o.name = N'DropColumnTest'
    AND s.name = N'dbo'
    AND dpa.page_type_desc = N'DATA_PAGE';
OPEN cur;
FETCH NEXT FROM cur INTO @fileid, @pageid;
WHILE @@FETCH_STATUS = 0
BEGIN
    DBCC PAGE (@dbid, @fileid, @pageid, 3);
    FETCH NEXT FROM cur INTO @fileid, @pageid;
END
CLOSE cur;
DEALLOCATE cur;
DBCC TRACEOFF(3604);

Nhìn vào đầu ra cho trang đầu tiên từ bản demo của tôi (sau khi cột bị bỏ, nhưng trước khi cột được cập nhật), tôi thấy điều này:

TRANG: (1: 100104)


ĐỆM:


BUF @ 0x0000021793E42040

bpage = 0x000002175A7A0000 bhash = 0x0000000000000000 bpageno = (1: 100104)
bdbid = 10 breferences = 1 bcputicks = 0
bsampleCount = 0 bUse1 = 13760 bstat = 0x10b
blog = 0x212121cc bnext = 0x0000000000000000 bDentyContext = 0x000002175004B640
bstat2 = 0x0                        

TRANG CHỦ ĐẦU TƯ:


Trang @ 0x000002175A7A0000

m_pageId = (1: 100104) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0xc000
m_objId (AllocUnitId.idObj) = 300 m_indexId (AllocUnitId.idInd) = 256 
Siêu dữ liệu: AllocUnitId = 72057594057588736                                
Siêu dữ liệu: PartitionId = 72057594051756032 Siêu dữ liệu: IndexId = 1
Siêu dữ liệu: ObjectId = 174623665 m_prevPage = (0: 0) m_nextPage = (0: 0)
pminlen = 8 m_slotCnt = 1 m_freeCnt = 79
m_freeData = 8111 m_reservedCnt = 0 m_lsn = (616: 14191: 25)
m_xactReserved = 0 m_xdesId = (0: 0) m_ghostRecCnt = 0
m_tornBits = 0 DB Frag ID = 1                      

Tình trạng phân bổ

GAM (1: 2) = SGAM ALLOCATED (1: 3) = KHÔNG ĐƯỢC PHÉP          
PFS (1: 97056) = 0x40 ĐƯỢC PHÉP 0_PCT_FULL DIFF (1: 6) = THAY ĐỔI
ML (1: 7) = KHÔNG MIN_LOGGED           

Khe 0 Offset 0x60 Chiều dài 8015

Loại bản ghi = PRIMARY_RECORD Thuộc tính bản ghi = NULL_BITMAP VARIABLE_COLUMNS
Kích thước bản ghi = 8015                  
Bộ nhớ kết xuất @ 0x000000B75227A060

0000000000000000: 30000800 01000000 02000001 004f1f5a 5a5a5a5a 0 ............ O.ZZZZZ
0000000000000014: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a Zzzzzzzzzzzzzzzzz
.
.
.
0000000000001F2C: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a
0000000000001F40: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a

Khe 0 Cột 1 Offset 0x4 Chiều dài 4 Chiều dài (vật lý) 4

bỏ = 1                             

Khe 0 Cột 67108865 Offset 0xf Chiều dài 0 Độ dài (vật lý) 8000

DROPPED = NULL                      

Khe 0 Offset 0x0 Chiều dài 0 Độ dài (vật lý) 0

KeyHashValue = (8194443284a0)       

Tôi đã loại bỏ hầu hết kết xuất trang thô khỏi đầu ra được hiển thị ở trên để đơn giản. Ở cuối đầu ra, bạn sẽ thấy điều này cho ridcột:

Khe 0 Cột 1 Offset 0x4 Chiều dài 4 Chiều dài (vật lý) 4

bỏ = 1                             

Dòng cuối cùng ở trên, rid = 1trả về tên của cột và giá trị hiện tại được lưu trữ trong cột trên trang.

Tiếp theo, bạn sẽ thấy điều này:

Khe 0 Cột 67108865 Offset 0xf Chiều dài 0 Độ dài (vật lý) 8000

DROPPED = NULL                      

Kết quả đầu ra cho thấy Slot 0 chứa một cột bị xóa, nhờ vào DELETEDvăn bản mà tên cột thường là. Giá trị của cột được trả về NULLvì cột đã bị xóa. Tuy nhiên, như bạn có thể thấy trong dữ liệu thô, giá trị dài 8.000 ký tự REPLICATE('Z', 8000), cho cột đó vẫn tồn tại trên trang. Đây là một mẫu của phần đầu ra TRANG DBCC:

0000000000001EDC: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a Zzzzzzzzzzzzzzzzz
0000000000001EF0: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a Zzzzzzzzzzzzzzzzzzz
0000000000001F04: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a
0000000000001F18: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a
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.