NVARCHAR (4000) 열을 NVARCHAR (260)로 빠르게 변경


12

이 테이블을 몇 개의 NVARCHAR(4000)열로 처리하는 매우 큰 메모리 부여와 관련된 성능 문제가 있습니다. 이 열은 결코 크지 않습니다 NVARCHAR(260).

사용

ALTER TABLE [table] ALTER COLUMN [col] NVARCHAR(260) NULL

결과적으로 SQL Server는 수십억 개의 행인 전체 테이블을 다시 작성하고 로그 공간에서 2 배 테이블 크기를 사용하므로 아무것도 변경하지 않고 옵션이 아닙니다. 열 너비를 늘리면이 문제가 발생하지 않지만 감소합니다.

나는 제약 만드는 시도 CHECK (DATALENGTH([col]) <= 520)또는 CHECK (LEN([col]) <= 260)및 SQL Server는 여전히 전체 테이블을 다시 쓰기로 결심한다.

메타 데이터 전용 작업으로 열 데이터 유형을 변경하는 방법이 있습니까? 전체 테이블을 다시 쓰는 비용없이? SQL Server 2017 (14.0.2027.2 및 14.0.3192.2)을 사용하고 있습니다.

다음은 재현하는 데 사용할 샘플 DDL 테이블입니다.

CREATE TABLE [table](
    id INT IDENTITY(1,1) NOT NULL,
    [col] NVARCHAR(4000) NULL,
    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

그런 다음을 실행하십시오 ALTER.

답변:


16

나는 당신이 찾고있는 것을 직접 성취하는 방법을 모른다. 현재 쿼리 최적화 프로그램은 메모리 부여 계산에 대한 제약 조건을 고려하기에 충분하지 않으므로 제약 조건은 도움이되지 않습니다. 테이블 데이터를 다시 쓰지 않는 몇 가지 방법 :

  1. 열을 사용하는 모든 코드에서 열을 NVARCHAR (260)로 캐스트하십시오. 쿼리 최적화 프로그램은 원시 데이터 형식 대신 캐스팅 된 데이터 형식을 사용하여 메모리 부여를 계산합니다.
  2. 테이블 이름을 바꾸고 대신 캐스트를 수행하는 뷰를 작성하십시오. 이것은 옵션 1과 동일한 기능을 수행하지만 업데이트해야하는 코드의 양을 제한 할 수 있습니다.
  3. 올바른 데이터 형식으로 비 지속적 계산 열을 만들고 모든 쿼리가 원래 열 대신 해당 열에서 선택되도록합니다.
  4. 기존 열의 이름을 바꾸고 원래 이름으로 계산 된 열을 추가하십시오. 그런 다음 새 열 이름을 대신 사용하도록 원래 열을 업데이트하거나 삽입하는 모든 쿼리를 조정하십시오.

15

메타 데이터 전용 작업으로 열 데이터 유형을 변경하는 방법이 있습니까?

나는 그렇게 생각하지 않는다. 이것이 바로 제품이 작동하는 방식이다. Joe의 답변 에서 제안한이 제한에 대한 몇 가지 훌륭한 해결 방법이 있습니다.

... SQL Server에서 전체 테이블을 다시 작성하고 로그 공간에서 2x 테이블 크기를 사용함

나는 그 진술의 두 부분에 개별적으로 응답 할 것입니다.

테이블 재 작성

앞에서 언급했듯이 이것을 피할 수있는 방법은 없습니다. 고객으로서 우리의 관점에서 완전히 이해가되지 않더라도 상황의 현실 인 것 같습니다.

보면 DBCC PAGE이전과 모든 데이터는 데이터 페이지에 중복되는 4000 260 쇼에서 열을 변경 한 후 (내 테스트 테이블은 한 'A'행 260 번) :

이전 및 이후 dbcc 페이지의 데이터 부분 스크린 샷

이 시점에서 페이지에는 정확히 동일한 데이터의 사본이 두 개 있습니다. "이전"열은 기본적으로 삭제되고 (ID는 id = 2에서 id = 67108865로 변경됨) "새"버전의 열은 페이지에서 데이터의 새 오프셋을 가리 키도록 업데이트됩니다.

이전 및 이후 dbcc 페이지의 열 메타 데이터 부분 스크린 샷

로그 공간에서 2x 테이블 크기 사용

명령문 WITH (ONLINE = ON)의 끝에 추가 하면 로깅 활동이 약 절반 으로 줄어들 기 때문에 필요한 디스크 / 디스크 공간에 대한 쓰기 양을 줄일 수있는 개선 사항 중 하나입니다.ALTER

나는이 시험 장치를 사용하여 그것을 시험해 보았다 :

USE [master];
GO
DROP DATABASE IF EXISTS [248749];
GO
CREATE DATABASE [248749] 
ON PRIMARY 
(
    NAME = N'248749', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749.mdf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
)
LOG ON 
(
    NAME = N'248749_log', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749_log.ldf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
);
GO
USE [248749];
GO

CREATE TABLE dbo.[table]
(
    id int IDENTITY(1,1) NOT NULL,
    [col] nvarchar (4000) NULL,

    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

INSERT INTO dbo.[table]
SELECT TOP (1000000)
    REPLICATE(N'A', 260)
FROM master.dbo.spt_values v1
    CROSS JOIN master.dbo.spt_values v2
    CROSS JOIN master.dbo.spt_values v3;
GO

sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)을 실행하기 전후에 확인한 ALTER결과 차이점은 다음과 같습니다.

기본 (오프라인) ALTER

  • 데이터 파일 쓰기 / 바이트 쓰기 : 34,809 / 2,193,801,216
  • 로그 파일 쓰기 / 바이트 쓰기 : 40,953 / 1,484,910,080

온라인 ALTER

  • 데이터 파일 쓰기 / 바이트 쓰기 : 36,874 / 1,693,745,152 (22.8 % 감소)
  • 로그 파일 쓰기 / 바이트 쓰기 : 24,680 / 866,166,272 (41 % 감소)

보시다시피, 데이터 파일 쓰기는 약간 떨어졌고 로그 파일 쓰기는 크게 떨어졌습니다.


2

나는 비슷한 상황에 여러 번 있었다.

단계 :

원하는 너비의 새 열 추가

커밋 당 수천 번 (약 10 만 또는 2 만 번)의 커서를 사용하여 이전 열에서 새 열로 데이터를 복사하십시오.

이전 열 삭제

새 열 이름을 이전 열 이름으로 바꿉니다.

타다!


3
이미 복사 한 일부 레코드가 업데이트 또는 삭제되면 어떻게됩니까?
George.Palacios

1
update table set new_col = old_col where new_col <> old_col;삭제하기 전에 하나의 최종 작업을 수행하는 것이 매우 쉽습니다 old_col.
Colin 't Hart

1
@ Colin'tHart 그 접근 방식은 수백만 행에서 작동하지 않습니다 ... 거래가
커지고

@samsmith 먼저 위에서 설명한 내용을 수행하십시오. 그런 다음 원래 열을 삭제하기 전에 그 동안 원래 데이터에 대한 업데이트가있는 경우 해당 업데이트 문을 실행하십시오. 수정 된 몇 개의 행에만 영향을 미칩니다. 아니면 뭔가 빠졌습니까?
Colin 't Hart

프로세스 중에 업데이트 된 행을 처리하기 위해 where new_col <> old_col다른 필터링 절이없는 전체 스캔을 피하려고 시도하면 이러한 변경 사항이 발생할 때이를 수행하고 프로세스가 끝날 때이를 제거하는 트리거를 추가 할 수 있습니다. 여전히 잠재적 인 성능 저하가 있지만 마지막에 한 번의 큰 히트 대신 프로세스 기간 동안 많은 양의 소량이 발생합니다 (테이블의 앱 업데이트 패턴에 따라 다름). .
David Spillett

1

데이터베이스의 사용 가능한 공간에 따라 대안이 있습니다.

  1. 테이블 (예를 들면의 정확한 복사본을 만들기 new_table당신이에서 단축 될 컬럼을 제외) NVARCHAR(4000)NVARCHAR(260):

    CREATE TABLE [new_table](
        id INT IDENTITY(1,1) NOT NULL,
        [col] NVARCHAR(260) NULL,
        CONSTRAINT [PK_test_new] PRIMARY KEY CLUSTERED (id ASC)
    );
  2. 유지 관리 창에서 다음 과 같이 간단한 방법으로 "손상된"테이블 ( table)에서 "고정 된"테이블 ( new_table)로 데이터를 복사하십시오 INSERT ... INTO ... SELECT .....

    SET IDENTITY_INSERT [new_table] ON
    GO
    INSERT id, col INTO [new_table] SELECT id, col from [table]
    GO
    SET IDENTITY_INSERT [new_table] OFF
    GO
  3. "손상된"테이블의 이름을 table다른 것으로 바꾸십시오 .

    EXEC sp_rename 'table', 'old_table';  
  4. 은 "고정"테이블의 이름을 변경 new_table합니다 table:

    EXEC sp_rename 'new_table', 'table';  
  5. 모든 것이 정상이면 "깨진"이름이 바뀐 테이블을 삭제하십시오.

     DROP TABLE [old_table]
     GO

당신은 간다.

질문에 답변

메타 데이터 전용 작업으로 열 데이터 유형을 변경하는 방법이 있습니까?

아니요. 현재 불가능

전체 테이블을 다시 쓰는 비용없이?

아니요.
( 나의 솔루션 및 기타를 참조하십시오. )


"선택 대상에 삽입"하면 막대한 트랜잭션 (수백만 또는 수십억 행)이 발생하여 DB가 수십 또는 수백 분 동안 정지 될 수 있습니다. (사용중인 경우 ldf를
대량
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.