Apakah ALTER TABLE ... DROP COLUMN benar-benar operasi metadata saja?


11

Saya telah menemukan beberapa sumber yang menyatakan ALTER TABLE ... DROP COLUMN adalah operasi meta-data saja.

Sumber

Bagaimana ini bisa terjadi? Apakah data selama DROP COLUMN tidak perlu dibersihkan dari indeks yang tidak berkerumun dan indeks / tumpukan berkerumun?

Selain itu, mengapa Microsoft Documents menyiratkan bahwa itu adalah operasi yang sepenuhnya dicatat?

Modifikasi yang dilakukan pada tabel dicatat dan sepenuhnya dapat dipulihkan. Perubahan yang memengaruhi semua baris dalam tabel besar, seperti menjatuhkan kolom atau, pada beberapa edisi SQL Server, menambahkan kolom NOT NULL dengan nilai default, bisa memakan waktu lama untuk menyelesaikan dan menghasilkan banyak catatan log . Jalankan pernyataan ALTER TABLE ini dengan kehati-hatian yang sama seperti pernyataan INSERT, UPDATE, atau DELETE yang memengaruhi banyak baris.

Sebagai pertanyaan sekunder: bagaimana mesin melacak kolom yang jatuh jika data tidak dihapus dari halaman yang mendasarinya?


2
Yah, saya pikir bahasa telah bertahan melalui banyak versi produk dan banyak lagi iterasi dokumentasi. Seiring waktu, semakin banyak operasi yang melibatkan kolom telah berubah secara online / metadata saja. Ini mungkin contoh spesifik yang buruk sekarang, tetapi tujuan kalimat itu hanya untuk memperingatkan Anda bahwa, secara umum, beberapa operasi perubahan mungkin ukuran operasi data dalam skenario tertentu , daripada daftar setiap skenario tertentu.
Aaron Bertrand

Jawaban:


14

Ada keadaan tertentu di mana menjatuhkan kolom bisa menjadi operasi meta-data-only. Definisi kolom untuk setiap tabel yang diberikan tidak termasuk dalam setiap halaman di mana baris disimpan, definisi kolom hanya disimpan dalam metadata database, termasuk sys.sysrowsets, sys.sysrscols, dll.

Ketika menjatuhkan kolom yang tidak direferensikan oleh objek lain, mesin penyimpanan hanya menandai definisi kolom sebagai tidak lagi hadir dengan menghapus rincian terkait dari berbagai tabel sistem. Tindakan menghapus meta-data membatalkan cache prosedur, mengharuskan kompilasi ulang setiap kali kueri selanjutnya referensi tabel itu. Karena kompilasi hanya mengembalikan kolom yang saat ini ada dalam tabel, detail kolom untuk kolom yang dijatuhkan bahkan tidak pernah diminta; mesin penyimpanan melewatkan byte yang disimpan di setiap halaman untuk kolom itu, seolah-olah kolom itu tidak ada lagi.

Ketika operasi DML berikutnya terjadi terhadap tabel, halaman yang terpengaruh ditulis ulang tanpa data untuk kolom yang dijatuhkan. Jika Anda membangun kembali indeks berkerumun atau tumpukan, semua byte untuk kolom yang dijatuhkan secara alami tidak ditulis kembali ke halaman pada disk. Ini secara efektif menyebarkan beban menjatuhkan kolom dari waktu ke waktu, membuatnya kurang terlihat.

Ada beberapa situasi di mana Anda tidak bisa menjatuhkan kolom, seperti ketika kolom dimasukkan dalam indeks, atau ketika Anda secara manual membuat objek statistik untuk kolom. Saya menulis posting blog yang menunjukkan kesalahan yang disajikan ketika mencoba mengubah kolom dengan objek statistik yang dibuat secara manual. Semantik yang sama berlaku ketika menjatuhkan kolom - jika kolom direferensikan oleh setiap objek lain, tidak bisa begitu saja dijatuhkan. Objek referensi harus diubah terlebih dahulu, kemudian kolom bisa dijatuhkan.

Ini cukup mudah untuk ditampilkan dengan melihat isi dari log transaksi setelah menjatuhkan kolom. Kode di bawah ini membuat tabel dengan satu kolom char panjang 8.000. Ia menambahkan satu baris, kemudian menjatuhkannya, dan menampilkan isi dari log transaksi yang berlaku untuk operasi drop. Catatan log menunjukkan modifikasi ke berbagai tabel sistem tempat definisi tabel dan kolom disimpan. Jika data kolom sebenarnya dihapus dari halaman yang dialokasikan untuk tabel, Anda akan melihat catatan log merekam data halaman yang sebenarnya; tidak ada catatan seperti itu.

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)

(Outputnya terlalu besar untuk ditampilkan di sini, dan dbfiddle.uk tidak akan mengizinkan saya mengakses fn_dblog)

Set output pertama menunjukkan log sebagai hasil dari pernyataan DDL menjatuhkan kolom. Set output kedua menunjukkan log setelah menjalankan pernyataan DML tempat kami memperbarui ridkolom. Di set hasil kedua, kita melihat catatan log yang menunjukkan penghapusan terhadap dbo.DropColumnTest, diikuti oleh masukkan ke dbo.DropColumnTest. Setiap Panjang Catatan Log adalah 8116, menunjukkan halaman yang sebenarnya telah diperbarui.

Seperti yang dapat Anda lihat dari output dari fn_dblogperintah dalam tes di atas, seluruh operasi yang sepenuhnya login. Ini berlaku untuk pemulihan sederhana, serta pemulihan penuh. Terminologi "sepenuhnya dicatat" mungkin disalahartikan karena modifikasi data tidak dicatat. Ini bukan apa yang terjadi - modifikasi adalah login, dan dapat sepenuhnya digulung kembali. Log hanya merekam halaman-halaman yang disentuh, dan karena tidak ada halaman data tabel yang dicatat oleh operasi DDL, baik itu DROP COLUMN, dan setiap rollback yang mungkin terjadi akan terjadi dengan sangat cepat, terlepas dari ukuran tabel.

Untuk sains , kode berikut akan membuang halaman data untuk tabel yang termasuk dalam kode di atas, menggunakan DBCC PAGE, gaya "3". Gaya "3" menunjukkan kami menginginkan tajuk halaman plus interpretasi per baris terperinci . Kode menggunakan kursor untuk menampilkan detail untuk setiap halaman dalam tabel, jadi Anda mungkin ingin memastikan Anda tidak menjalankan ini pada tabel besar.

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

Melihat output untuk halaman pertama dari demo saya (setelah kolom dijatuhkan, tetapi sebelum kolom diperbarui), saya melihat ini:

HALAMAN: (1: 100104)


PENYANGGA:


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 bDirtyContext = 0x000002175004B640
bstat2 = 0x0                        

KEPALA HALAMAN:


Halaman @ 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 
Metadata: AllocUnitId = 72057594057588736                                
Metadata: PartitionId = 72057594051756032 Metadata: IndexId = 1
Metadata: 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                      

Status Alokasi

GAM (1: 2) = ALOKASI SGAM (1: 3) = TIDAK DIALOKASI          
PFS (1: 97056) = 0x40 ALLOCATED 0_PCT_FULL DIFF (1: 6) = CHANGED
ML (1: 7) = TIDAK MIN_LOGGED           

Slot 0 Offset 0x60 Panjang 8015

Jenis Catatan = PRIMARY_RECORD Atribut Catatan = NULL_BITMAP VARIABLE_COLUMNS
Ukuran Rekaman = 8015                  
Memory Dump @ 0x000000B75227A060

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

Slot 0 Kolom 1 Offset 0x4 Panjang 4 Panjang (fisik) 4

rid = 1                             

Slot 0 Kolom 67108865 Offset 0xf Panjang 0 Panjang (fisik) 8000

DROPPED = NULL                      

Slot 0 Offset 0x0 Panjang 0 Panjang (fisik) 0

KeyHashValue = (8194443284a0)       

Saya telah menghapus sebagian besar dump halaman mentah dari output yang ditunjukkan di atas untuk singkatnya. Di akhir output, Anda akan melihat ini untuk ridkolom:

Slot 0 Kolom 1 Offset 0x4 Panjang 4 Panjang (fisik) 4

rid = 1                             

Baris terakhir di atas,, rid = 1mengembalikan nama kolom, dan nilai saat ini disimpan dalam kolom di halaman.

Selanjutnya, Anda akan melihat ini:

Slot 0 Kolom 67108865 Offset 0xf Panjang 0 Panjang (fisik) 8000

DROPPED = NULL                      

Output menunjukkan bahwa Slot 0 berisi kolom yang dihapus, berdasarkan DELETEDteks di mana nama kolom biasanya. Nilai kolom dikembalikan NULLkarena kolom telah dihapus. Namun, seperti yang dapat Anda lihat di data mentah, nilai karakter 8.000 REPLICATE('Z', 8000), untuk kolom itu masih ada di halaman. Ini adalah contoh dari bagian dari output PAGE DBCC:

0000000000001EDC: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F04: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F18: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZ
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.