ALTER TABLE… DROP COLUMN은 실제로 메타 데이터 전용 작업입니까?


11

ALTER TABLE ... DROP COLUMN은 메타 데이터 전용 작업이라는 여러 소스를 찾았습니다.

출처

어떻게 이럴 수있어? DROP COLUMN 중 데이터를 기본 비 클러스터형 인덱스 및 클러스터형 인덱스 / 힙에서 제거 할 필요가 없습니까?

또한 Microsoft Docs가 왜 완전히 기록 된 작업임을 암시합니까?

테이블에 대한 수정 사항이 기록되고 완전히 복구 가능합니다. 열 삭제 또는 일부 SQL Server 버전에서 NOT NULL 열을 기본값으로 추가하는 등 큰 테이블의 모든 행에 영향을주는 변경은 많은 로그 레코드를 완료하고 생성하는 데 시간이 오래 걸릴 수 있습니다 . 많은 행에 영향을주는 INSERT, UPDATE 또는 DELETE 문과 동일한주의를 기울여 ALTER TABLE 문을 실행하십시오.

두 번째 질문으로, 데이터가 기본 페이지에서 제거되지 않은 경우 엔진은 삭제 된 열을 어떻게 추적합니까?


2
글쎄, 나는 많은 버전의 제품과 더 많은 문서 반복을 통해 언어가 살아남 았다고 생각합니다. 시간이 지남에 따라 열과 관련된 점점 더 많은 작업이 온라인 / 메타 데이터 만 변경되었습니다. 아마도 지금은 나쁜 예일 것입니다. 그러나 문장의 목적은 일반적으로 일부 변경 작업 모든 특정 시나리오에서 나열되는 것이 아니라 특정 시나리오 에서 데이터 크기 작업 일 수 있음 을 경고 하기위한 것입니다.
Aaron Bertrand

답변:


14

열을 삭제하는 것이 메타 데이터 전용 작업 일 수있는 특정 상황이 있습니다. 주어진 테이블에 대한 열 정의는 행이 저장되는 모든 페이지에 포함되지 않으며 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.