ALTER COLUMN을 NULL로 설정하지 않는 이유는 무엇입니까?


56

64m 행의 데이터가 디스크에서 4.3GB를 차지하는 테이블이 있습니다.

각 행은 약 30 바이트의 정수 열과 NVARCHAR(255)텍스트 의 변수 열입니다.

data-type과 함께 NULLABLE 열을 추가했습니다 Datetimeoffset(0).

그런 다음 모든 행에 대해이 열을 업데이트하고 모든 새로운 삽입물 이이 열에 값을 배치하는지 확인했습니다.

NULL 항목이 없으면이 명령을 실행하여 새 필드를 필수로 만들었습니다.

ALTER TABLE tblCheckResult 
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL

그 결과 공간이 부족해질 때까지 6GB에서 36GB 이상으로 트랜잭션 로그 크기가 크게 증가했습니다!

이 간단한 명령으로 SQL Server 2008 R2가 무엇을하고 있는지에 대해 아는 사람이 있습니까?


7
SQL Server 2012 Enterprise 에는NOT NULL 메타 데이터 작업으로 기본값을 사용하여 열 을 추가하는 기능 이 추가 되었습니다. 또한에서 "온라인 작업으로 NULL NOT 열 추가"를 참조 문서 .
폴 화이트

답변:


48

열을 NOT NULL로 변경하면 NULL 값이 없더라도 SQL Server는 모든 단일 페이지 를 터치 해야합니다. 채우기 비율에 따라 실제로 많은 페이지 분할이 발생할 수 있습니다. 물론 만지는 모든 페이지는 기록되어야하며, 분할로 인해 많은 페이지에 대해 두 가지 변경 사항이 기록되어야 할 것으로 생각됩니다. 그러나 모두 단일 패스로 수행되므로 로그는 모든 변경 사항을 처리해야하므로 취소를 누르면 취소 할 내용을 정확하게 알 수 있습니다.


예입니다. 간단한 테이블 :

DROP TABLE dbo.floob;
GO

CREATE TABLE dbo.floob
(
  id INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, 
  bar INT NULL
);

INSERT dbo.floob(bar) SELECT NULL UNION ALL SELECT 4 UNION ALL SELECT NULL;

ALTER TABLE dbo.floob ADD CONSTRAINT df DEFAULT(0) FOR bar

이제 페이지 세부 사항을 봅시다. 먼저 우리가 다루고있는 페이지와 DB_ID를 찾아야합니다. 제 경우에는이라는 데이터베이스를 만들었고 fooDB_ID는 5였습니다.

DBCC TRACEON(3604, -1);
DBCC IND('foo', 'dbo.floob', 1);
SELECT DB_ID();

출력 결과는 159 페이지에 관심이 있음을 나타냅니다 (의 유일한 DBCC IND출력 행 PageType = 1).

이제 OP 시나리오를 단계별로 진행하면서 일부 페이지 세부 정보를 살펴 보겠습니다.

DBCC PAGE(5, 1, 159, 3);

여기에 이미지 설명을 입력하십시오

UPDATE dbo.floob SET bar = 0 WHERE bar IS NULL;    
DBCC PAGE(5, 1, 159, 3);

여기에 이미지 설명을 입력하십시오

ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
DBCC PAGE(5, 1, 159, 3);

여기에 이미지 설명을 입력하십시오

이제는 내부 전문가가 아니기 때문에 이에 대한 답변이 없습니다. 그러나 업데이트 작업과 NOT NULL 제약 조건을 추가하면 페이지에 명백하게 쓸 수 있지만 후자는 완전히 다른 방식으로 수행됩니다. Null을 허용하지 않는 열을 Null을 허용하지 않는 열로 교체하여 비트 만 사용하는 것이 아니라 레코드의 구조를 실제로 변경하는 것 같습니다. 그것이 그렇게 해야하는 이유는 확실하지 않습니다 . 스토리지 엔진 팀 에게 좋은 질문입니다 . SQL Server 2012는 이러한 시나리오 중 일부를 훨씬 더 잘 처리한다고 믿습니다. FWIW-아직 철저한 테스트를 수행하지 않았습니다.


4
이 동작은 이후 버전의 SQL Server에서 상당히 변경되었습니다. 2016 RC2를 확인 하고이 정확한 시나리오와 테이블의 백만 개의 행에 대해 모든 값이 이미 열에 지정된 경우 NULL에서 NOT NULL로 변경하는 동안 29 개의 로그 레코드 만 생성된다는 것을 알았습니다.
Endrju

32

명령을 수행 할 때

ALTER COLUMN ... NOT NULL

이것은 열 추가, 업데이트, 열 삭제 작업으로 구현 된 것으로 보입니다.

  • 새 열 sys.sysrscols을 나타 내기 위해 새 행이 삽입 됩니다. status에 대한 비트 128허용하지 않는 열을 나타내는 설정 NULL
  • 새 컬럼 값을 이전 컬럼 값의 값으로 설정하여 테이블의 모든 행에서 갱신이 수행됩니다. "이전"및 "이후"버전의 행이 정확히 동일한 경우 트랜잭션 로그에 어떤 것도 기록되지 않으므로 업데이트가 기록됩니다.
  • 원래 열은 삭제 된 것으로 표시됩니다 (이는 메타 데이터 만 변경됩니다 sys.sysrscols. rscolid큰 정수로 업데이트되고 status비트 2는 삭제 된 것으로 설정 됨).
  • sys.sysrscols새 열에 대한 항목 은 이전 열의 항목 을 제공하도록 변경 rscolid됩니다.

많은 로깅을 유발할 있는 작업 UPDATE은 테이블의 모든 행에 해당하지만 항상 발생 한다는 의미는 아닙니다 . 행의 "이전"및 "이후"이미지가 동일하면 업데이트되지 않은 업데이트 로 취급되며 지금까지 테스트에서 기록되지 않습니다.

따라서 많은 로깅을받는 이유에 대한 설명은 행의 "이전"및 "이후"버전이 정확히 다른 이유에 따라 다릅니다.

FixedVar형식으로 저장된 가변 길이 열의 경우 설정하면 NOT NULL항상 기록 해야하는 행이 변경됩니다. 열 수와 가변 길이 열 수는 모두 증가하고 새 열은 데이터를 복제하는 가변 길이 섹션의 끝에 추가됩니다.

datetimeoffset(0)그러나 길이는 고정 길이이며 FixedVar형식에 저장된 고정 길이 열의 경우 이전 및 새 열 모두 행의 고정 길이 데이터 부분에 동일한 슬롯이 제공되고 둘 다 동일한 길이와 값을 갖기 때문에 "이전"및 "after"버전의 행은 동일 합니다. 이것은 @Aaron의 답변에서 볼 수 있습니다. 이전과 이후의 행의 두 버전 ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;아르

0x10000c00 01000000 00000000 020000

이것은 기록되지 않습니다.

논리적으로 이벤트에 대한 설명에서 열 수 02를 늘려야 03하지만 실제로는 실제로 그러한 변경이 발생하지 않으므로 행이 실제로 달라져야합니다.

고정 길이 열에서 이것이 발생할 수있는 몇 가지 이유는 다음과 같습니다.

  • 열이 원래 선언 된 SPARSE경우 새 열은 원래 행과 다른 행 부분에 저장되어 이전 및 이후 행 이미지가 달라집니다.
  • 압축 옵션을 사용하는 경우 CD 어레이의 열 수 섹션이 증가함에 따라 행의 이전 및 이후 버전이 달라집니다.
  • 스냅 샷 격리 옵션 중 하나가 활성화 된 데이터베이스에서 각 행의 버전 정보가 업데이트됩니다 (@SQL Kiwi 는 여기에 설명 된대로 SI가 활성화되지 않은 데이터베이스에서도 발생할 수 있음을 나타냅니다 ).
  • ALTER TABLE메타 데이터로만 변경되어 행에 아직 적용되지 않은 일부 이전 작업 이있을 수 있습니다 . 예를 들어, 새로운 널 입력 가능 가변 길이 열이 추가 된 경우 이는 원래 메타 데이터 전용 변경 사항으로 적용되며 다음에 갱신 될 때 행에만 실제로 기록됩니다 (이 마지막 인스턴스에서 실제로 발생하는 쓰기는 단지 열 카운트 부와 NULL_BITMAPA와 NULL varchar행의 마지막 열은 모든 공간을 차지하지 않는다)

5

200.000.000 개의 행이있는 테이블과 관련하여 동일한 문제에 직면했습니다. 처음에는 열을 nullable로 추가 한 다음 모든 행을 업데이트 NOT NULL하고 ALTER TABLE ALTER COLUMN명령문 을 통해 열을 변경했습니다 . 그 결과 두 개의 큰 트랜잭션이 로그 파일을 엄청나게 불어 넣었습니다 (170GB 증가).

내가 찾은 가장 빠른 방법은 다음과 같습니다.

  1. 기본값을 사용하여 열 추가

    ALTER TABLE table1 ADD column1 INT NOT NULL DEFAULT (1)
  2. 제약 조건이 이전에 명명되지 않았으므로 동적 SQL을 사용하여 기본 제약 조건을 삭제하십시오.

    DECLARE 
        @constraint_name SYSNAME,
        @stmt NVARCHAR(510);
    
    SELECT @CONSTRAINT_NAME = DC.NAME
    FROM SYS.DEFAULT_CONSTRAINTS DC
    INNER JOIN SYS.COLUMNS C
        ON DC.PARENT_OBJECT_ID = C.OBJECT_ID
        AND DC.PARENT_COLUMN_ID = C.COLUMN_ID
    WHERE
        PARENT_OBJECT_ID = OBJECT_ID('table1')
        AND C.NAME = 'column1';
    

트랜잭션 복제를 통한 변경 사항 복제를 포함하여 실행 시간이 30 분에서 10 분으로 줄었습니다. SQL Server 2008 설치 (SP2)를 실행하고 있습니다.


2

다음 테스트를 실행했습니다.

create table tblCheckResult(
        ColID   int identity
    ,   dtoDateTime Datetimeoffset(0) null
    )

 go

insert into tblCheckResult (dtoDateTime)
select getdate()
go 10000

checkpoint 

ALTER TABLE tblCheckResult 
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL

select * from fn_dblog(null,null)

나는 이것이 트랜잭션을 롤백 할 때를 대비하여 로그가 보유한 예약 공간과 관련이 있다고 생각합니다. LOP_BEGIN_XACT 행의 '로그 예약'열에서 fn_dblog 함수를보고 예약하려는 공간의 양을 확인하십시오.


시도 select * FROM fn_dblog(null, null) where AllocUnitName='dbo.tblCheckResult' AND Operation = 'LOP_MODIFY_ROW'하면 10000 행 업데이트를 볼 수 있습니다.
Martin Smith

-2

이에 대한 동작은 SQL Server 2012와 다릅니다. http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/을 참조하십시오.

SQL Server 2008 R2 이하 릴리스에 대해 생성 된 로그 레코드의 수는 SQL Server 2012의 로그 레코드 수보다 훨씬 높습니다.


2
문제는 기존 열을 변경하여 NOT NULL로깅 을 발생 시키는 이유 입니다. 2012 년의 변경 사항은 NOT NULL기본값으로 새 열을 추가하는 것 입니다.
Martin Smith
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.