열을 삭제하는 것이 메타 데이터 전용 작업 일 수있는 특정 상황이 있습니다. 주어진 테이블에 대한 열 정의는 행이 저장되는 모든 페이지에 포함되지 않으며 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 문이 열을 삭제 한 결과로 로그를 표시합니다. 두 번째 출력 세트는 rid
열 을 업데이트하는 DML 문을 실행 한 후 로그를 보여줍니다 . 두 번째 결과 집합에는 dbo.DropColumnTest에 대한 삭제를 나타내는 로그 레코드와 dbo.DropColumnTest에 대한 삽입이 차례로 표시됩니다. 각 로그 레코드 길이는 8116이며 실제 페이지가 업데이트되었음을 나타냅니다.
fn_dblog
위 테스트 의 명령 출력에서 볼 수 있듯이 전체 작업 이 완전히 기록됩니다. 이것은 전체 복구뿐만 아니라 간단한 복구에도 사용됩니다. 데이터 수정이 기록되지 않아 "완전히 기록 된"이라는 용어가 잘못 해석 될 수 있습니다. 이것은 발생하지 않습니다-수정 이 기록되고 완전히 롤백 될 수 있습니다. 로그는 단순히 터치 한 페이지 만 기록하며 DDL 작업으로 테이블의 데이터 페이지가 기록되지 않았기 때문에 DROP COLUMN
, 및 롤백이 발생하면 테이블 크기에 관계없이 매우 빠르게 발생합니다.
과학 의 경우 다음 코드는 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 참조 = 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 Frag ID = 1
할당 상태
GAM (1 : 2) = 할당 된 SGAM (1 : 3) = 할당되지 않은
PFS (1 : 97056) = 0x40 할당 된 0_PCT_FULL DIFF (1 : 6) = 변경됨
ML (1 : 7) = NOT 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 ZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F40 : 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a ZZZZZZZZZZZZZZZ
슬롯 0 열 1 오프셋 0x4 길이 4 길이 (물리적) 4
제거 = 1
슬롯 0 열 67108865 오프셋 0xf 길이 0 길이 (물리적) 8000
삭제 = NULL
슬롯 0 오프셋 0x0 길이 0 길이 (물리) 0
KeyHashValue = (8194443284a0)
간결성을 위해 위의 출력에서 대부분의 원시 페이지 덤프를 제거했습니다. 출력이 끝나면 rid
열에 대해 다음을 볼 수 있습니다 .
슬롯 0 열 1 오프셋 0x4 길이 4 길이 (물리적) 4
제거 = 1
위의 마지막 줄 rid = 1
은 열의 이름과 페이지의 열에 저장된 현재 값을 반환합니다.
다음으로 이것을 볼 수 있습니다 :
슬롯 0 열 67108865 오프셋 0xf 길이 0 길이 (물리적) 8000
삭제 = NULL
출력은 DELETED
열 이름이 일반적으로있는 텍스트로 인해 슬롯 0에 삭제 된 열이 포함되어 있음을 보여줍니다 . NULL
열이 삭제 된 후 열 값이 리턴 됩니다. 그러나 원시 데이터에서 볼 수 있듯이 REPLICATE('Z', 8000)
해당 열의 8,000 자 길이 값 은 페이지에 계속 존재합니다. 다음은 DBCC PAGE 출력 부분의 샘플입니다.
EDC : 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0 : 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F04 : 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001F18 : 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ