列の削除がメタデータのみの操作になる場合がある特定の状況があります。特定のテーブルの列定義は、行が格納されているすべてのページに含まれていません。列定義は、sys.sysrowsets、sys.sysrscolsなどのデータベースメタデータにのみ保存されます。
他のオブジェクトから参照されていない列を削除する場合、ストレージエンジンは、さまざまなシステムテーブルから関連する詳細を削除することにより、列定義を存在しないものとしてマークします。メタデータを削除すると、プロシージャキャッシュが無効になり、クエリが後でそのテーブルを参照するたびに再コンパイルが必要になります。再コンパイルでは現在テーブルに存在する列のみが返されるため、削除された列の列の詳細は要求されません。ストレージエンジンは、列が存在しないかのように、その列の各ページに格納されているバイトをスキップします。
テーブルに対して後続のDML操作が発生すると、影響を受けるページは、ドロップされた列のデータなしで再書き込みされます。クラスター化インデックスまたはヒープを再構築する場合、削除された列のすべてのバイトはディスク上のページに書き戻されません。これにより、時間の経過とともにカラムを落下させる負荷が効果的に分散され、目立たなくなります。
列がインデックスに含まれている場合や、列の統計オブジェクトを手動で作成した場合など、列を削除できない状況があります。手動で作成した統計オブジェクトで列を変更しようとしたときに表示されるエラーを示すブログ投稿を書きました。列を削除する場合も同じセマンティクスが適用されます。列が他のオブジェクトによって参照されている場合、単に削除することはできません。参照するオブジェクトを最初に変更してから、列を削除する必要があります。
これは、列を削除した後にトランザクションログの内容を確認することで、かなり簡単に表示できます。以下のコードは、単一の8,000の長いchar列を持つテーブルを作成します。行を追加してからドロップし、ドロップ操作に適用できるトランザクションログの内容を表示します。ログレコードは、テーブルと列の定義が格納されているさまざまなシステムテーブルへの変更を示します。列データがテーブルに割り当てられたページから実際に削除されていた場合、実際のページデータを記録するログレコードが表示されます。そのような記録はありません。
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)
(出力が大きすぎてここに表示できません。dbfiddle.ukではfn_dblogにアクセスできません)
最初の出力セットは、DDLステートメントが列を削除した結果のログを示しています。2番目の出力セットは、rid
列を更新するDMLステートメントを実行した後のログを示しています。2番目の結果セットでは、dbo.DropColumnTestに対する削除と、それに続くdbo.DropColumnTestへの挿入を示すログレコードが表示されます。各ログレコード長は8116で、実際のページが更新されたことを示します。
fn_dblog
上記のテストのコマンドの出力からわかるように、操作全体が完全にログに記録されています。これは、単純な回復だけでなく、完全な回復にも当てはまります。「完全にログに記録された」という用語は、データの変更がログに記録されないため、誤って解釈される可能性があります。これは何が起こるかではありません-変更はログに記録され、完全にロールバックできます。ログは単にタッチされたページのみを記録しており、テーブルのデータページはDDLオペレーションによってログに記録されDROP COLUMN
なかったため、とロールバックの両方が、テーブルのサイズに関係なく、非常に迅速に発生します。
scienceの場合、次のコードはDBCC PAGE
、スタイル "3" を使用して、上記のコードに含まれているテーブルのデータページをダンプします。スタイル「3」は、ページヘッダーと行ごとの詳細な解釈が必要であることを示します。コードはカーソルを使用してテーブル内のすべてのページの詳細を表示するため、大きなテーブルではこれを実行しないようにする必要がある場合があります。
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);
デモの最初のページの出力(列が削除された後、列が更新される前)を見ると、次のことがわかります。
ページ:(1:100104)
バッファ:
BUF @ 0x0000021793E42040
bpage = 0x000002175A7A0000 bhash = 0x0000000000000000 bpageno =(1:100104)
bdbid = 10 breferences = 1 bcputicks = 0
bsampleCount = 0 bUse1 = 13760 bstat = 0x10b
ブログ= 0x212121cc bnext = 0x0000000000000000 bDirtyContext = 0x000002175004B640
bstat2 = 0x0
ページヘッダー:
ページ@ 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
メタデータ:AllocUnitId = 72057594057588736
メタデータ:PartitionId = 72057594051756032メタデータ:IndexId = 1
メタデータ: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フラグメントID = 1
割り当てステータス
GAM(1:2)=割り当て済みSGAM(1:3)=割り当てなし
PFS(1:97056)= 0x40 ALLOCATED 0_PCT_FULL DIFF(1:6)=変更されました
ML(1:7)= MIN_LOGGEDではない
スロット0オフセット0x60長さ8015
レコードタイプ= PRIMARY_RECORDレコード属性= NULL_BITMAP VARIABLE_COLUMNS
レコードサイズ= 8015
メモリダンプ@ 0x000000B75227A060
0000000000000000:30000800 01000000 02000001 004f1f5a 5a5a5a5a 0 ............ O.ZZZZZ
0000000000000014:5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
。
。
。
0000000000001F2C:5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001F40:5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a ZZZZZZZZZZZZZZZ
スロット0列1オフセット0x4長さ4長さ(物理)4
rid = 1
スロット0列67108865オフセット0xf長さ0長さ(物理)8000
削除= NULL
スロット0オフセット0x0長さ0長さ(物理)0
KeyHashValue =(8194443284a0)
簡潔にするため、上記の出力から生のページダンプのほとんどを削除しました。出力の最後に、このrid
列が表示されます。
スロット0列1オフセット0x4長さ4長さ(物理)4
rid = 1
上の最後の行rid = 1
は、列の名前と、ページの列に格納されている現在の値を返します。
次に、これが表示されます:
スロット0列67108865オフセット0xf長さ0長さ(物理)8000
削除= NULL
出力はDELETED
、列名が通常あるテキストのおかげで、スロット0に削除された列が含まれていることを示しています。NULL
列が削除されてからの列の値が返されます。ただし、生データを見るとわかるように、REPLICATE('Z', 8000)
その列の8,000文字の長さの値は、ページにまだ存在しています。これは、DBCC PAGE出力のその部分のサンプルです。
0000000000001EDC:5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0:5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001F04:5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001F18:5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ