COALESCE를 사용하여 기본 키를 IDENTITY에서 지속되는 것으로 변경


10

모 놀리 식 데이터베이스에서 응용 프로그램을 분리하기 위해 다양한 테이블의 INT IDENTITY 열을 COALESCE를 사용하는 PERSISTED 계산 열로 변경하려고 시도했습니다. 기본적으로 분리 된 응용 프로그램에는 많은 응용 프로그램에서 공유되는 공통 데이터에 대한 데이터베이스를 계속 업데이트하면서 기존 응용 프로그램이 코드 나 프로 시저를 수정하지 않고도 이러한 테이블에 데이터를 만들 수있는 기능이 필요합니다.

본질적으로, 우리는 열 정의에서 이동했습니다;

PkId INT IDENTITY(1,1) PRIMARY KEY

에;

PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL

모든 경우에 PkId는 PRIMARY KEY이며 한 경우를 제외하고는 모두 CLUSTERED입니다. 모든 테이블에는 이전과 동일한 외래 키와 인덱스가 있습니다. 본질적으로 새 형식을 사용하면 분리 된 응용 프로그램에서 external_id로 PkId를 제공 할 수 있지만 PkId는 IDENTITY 열 값이 될 수 있으므로 SCOPE_IDENTITY 및 @@ IDENTITY를 사용하여 IDENTITY 열에 의존하는 기존 코드를 허용합니다. 예전처럼 작동합니다.

우리가 가진 문제는 수용 가능한 시간 내에 실행되어 이제 완전히 터져 나오는 몇 가지 쿼리를 발견했다는 것입니다. 이러한 쿼리에서 사용 된 생성 된 쿼리 계획은 이전과 같지 않습니다.

새 열이 이전과 동일한 데이터 유형 인 PRIMARY KEY이고 PERSISTED 인 경우 쿼리 및 쿼리 계획이 이전과 동일하게 작동 할 것으로 예상했습니다. COMPUTED PERSISTED INT PkId는 SQL Server가 실행 계획을 생성하는 방식에있어 명시 적 INT 정의와 본질적으로 동일한 방식으로 작동해야합니까? 이 접근 방식에서 볼 수있는 다른 문제가 있습니까?

이 변경의 목적은 기존 프로 시저와 코드를 수정하지 않고도 테이블 정의를 변경할 수 있도록하는 것이 었습니다. 이러한 문제를 감안할 때 우리는이 접근법으로 갈 수 있다고 생각하지 않습니다.


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Paul White 9

답변:


4

먼저

당신은 아마 모든 세 개의 열 필요가 없습니다 old_id, external_id, new_id. 에있는 new_id열은에 IDENTITY삽입하더라도 각 행에 대해 새 값이 생성됩니다 external_id. 그러나, 사이 old_idexternal_id, 사람들은 거의 상호 배타적입니다 : 이미 존재 중 하나 old_id또는 현재의 개념에 해당 열이, 바로 될 것입니다 NULL사용하는 경우 external_idnew_id. 이미 존재하는 행 (예 : old_id값 이있는 행)에 새 "외부"ID를 추가 하지 않고에 대한 새 값이 들어오지 않으므로 old_id사용되는 열이 하나있을 수 있습니다. 두 가지 목적을 위해.

따라서 external_id열을 제거 하고 이름 old_id을 바꾸십시오 old_or_external_id. 이것은 실제 변경이 필요하지 않지만 일부 합병증을 줄입니다. external_id앱 코드가 이미 삽입되도록 작성된 경우 "이전"값을 포함하더라도 열을 호출해야 할 수도 있습니다 external_id.

새로운 구조는 다음과 같습니다.

PkId AS AS COALESCE(old_or_external_id, new_id, -1) PERSISTED NOT NULL,
old_or_external_id INT NULL, -- values from existing record OR passed in from app
new_id INT IDENTITY(2000000, 1) NOT NULL

이제 SPARSE옵션 또는 데이터 압축을 사용하지 않는 경우 12 바이트 대신 행당 8 바이트 만 추가했습니다 . 또한 코드, T-SQL 또는 앱 코드를 변경할 필요가 없습니다.

둘째

이 단순화 과정을 계속하면서 우리가 남긴 것을 살펴 보겠습니다.

  • old_or_external_id열은 이미 하나의 값을 가지거나, 또는 애플리케이션에서 새로운 값을 부여되거나, 방치한다 NULL.
  • new_id항상 생성 된 새 값이되지만, 경우에 그 값은 사용되는 old_or_external_id열이다 NULL.

old_or_external_id및에 값이 필요한 시간은 없습니다 new_id. 그렇습니다. 두 열에 모두 값 new_id이있는 IDENTITY경우가 있지만 해당 new_id값은 무시됩니다. 이 두 필드는 상호 배타적입니다. 그래서 지금 무엇?

이제 왜 우리 external_id가 처음에 필요한지를 살펴볼 수 있습니다 . 을 IDENTITY사용하여 열에 삽입 할 수 있다는 것을 고려 SET IDENTITY_INSERT {table_name} ON;하면 스키마를 전혀 변경하지 않고도 응용 프로그램 코드 만 수정하여 INSERT명령문 / 연산 SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;명령문 을 래핑 할 수 있습니다 . 그런 다음 IDENTITY열을 (새로 생성 된 값의 경우) 재설정 할 시작 범위를 결정 해야합니다. 더 높은 값을 삽입하면 다음 자동 생성 값이 발생하기 때문에 앱 코드가 삽입 할 값보다 훨씬 높아야합니다. 현재 MAX 값보다 커야합니다. 그러나 항상 IDENT_CURRENT 값 보다 작은 값을 삽입 할 수 있습니다 .

old_or_external_idnew_id열을 결합 해도 2 개 또는 3 개의 열을 갖는 의도는 열을 기본 키 값으로 결합하기 때문에 그것들은 항상 고유 한 값입니다.

이 방법에서는 다음을 수행하면됩니다.

  • 테이블을 그대로 둡니다.

    PkId INT IDENTITY(1,1) PRIMARY KEY

    이렇게하면 8 또는 12가 아닌 각 행에 0 바이트가 추가됩니다.

  • 앱 생성 값의 시작 범위를 결정하십시오. 이 값은 각 테이블의 현재 MAX 값보다 크지 만 자동 생성 된 값의 최소값보다 작습니다.
  • 자동 생성 범위가 시작되는 값을 결정하십시오. 현재 MAX 값 성장할 공간 사이에 충분한 공간이 있어야합니다. 상한값은 21 억 4 천만 이상입니다. 그런 다음 DBCC CHECKIDENT 를 통해이 새로운 최소 시드 값을 설정할 수 있습니다 .
  • 앱 코드 인서트를 래핑 SET IDENTITY_INSERT {table_name} ON;하고 SET IDENTITY_INSERT {table_name} OFF;문으로 묶습니다 .

두 번째, 파트 B

바로 위에서 언급 한 접근 방식의 변형은 앱 코드가 -1로 시작하여 그로부터 내려가는 값을 삽입 하는 것입니다. 이것은 잎 IDENTITY가는 유일한 사람 것으로 값을 최대 . 여기서 이점은 스키마를 복잡하게 할뿐만 아니라 앱 생성 값이 새로운 자동 생성 범위로 실행되는 경우 겹치는 ID로 실행되는 것에 대해 걱정할 필요가 없다는 것입니다. 마이너스 ID 값을 아직 사용하지 않는 경우에만 옵션입니다. 사람들이 자동 생성 열에 마이너스 값을 사용하는 경우는 거의 없으므로 대부분의 상황에서 가능성이 높습니다.

이 방법에서는 다음을 수행하면됩니다.

  • 테이블을 그대로 둡니다.

    PkId INT IDENTITY(1,1) PRIMARY KEY

    이렇게하면 8 또는 12가 아닌 각 행에 0 바이트가 추가됩니다.

  • 앱에서 생성 된 값의 시작 범위는입니다 -1.
  • 앱 코드 인서트를 래핑 SET IDENTITY_INSERT {table_name} ON;하고 SET IDENTITY_INSERT {table_name} OFF;문으로 묶습니다 .

여기서는 여전히을 수행해야 IDENTITY_INSERT하지만 새 열을 추가하지 않고 열을 "다시 시드"할 필요 IDENTITY가 없으며 향후 겹칠 위험이 없습니다.

두 번째, 파트 3

이 접근 방식의 마지막 변형 중 하나는 IDENTITY열을 교체 하고 대신 Sequences 를 사용하는 것 입니다. 이 방법을 사용하는 이유는 앱 코드가 양수, 자동 생성 범위 이상 (아래 아님) 및 필요없는 값을 삽입하도록하기 위해서 SET IDENTITY_INSERT ON / OFF입니다.

이 방법에서는 다음을 수행하면됩니다.

  • CREATE SEQUENCE를 사용하여 시퀀스 만들기
  • 속성이 없지만 NEXT VALUE FOR 함수를 사용하여 제약 조건 이 IDENTITY있는 새 열에 열을 복사하십시오 .IDENTITYDEFAULT

    PkId INT PRIMARY KEY CONSTRAINT [DF_TableName_NextID] DEFAULT (NEXT VALUE FOR...)

    이렇게하면 8 또는 12가 아닌 각 행에 0 바이트가 추가됩니다.

  • 앱에서 생성 된 값의 시작 범위는 자동 생성 된 값이 접근 할 수있는 수준보다 훨씬 높습니다.
  • 앱 코드 인서트를 래핑 SET IDENTITY_INSERT {table_name} ON;하고 SET IDENTITY_INSERT {table_name} OFF;문으로 묶습니다 .

그러나 시퀀스 중 하나를 사용 SCOPE_IDENTITY()하거나 @@IDENTITY여전히 올바르게 작동 하는 코드의 요구 사항으로 인해 시퀀스로 전환하는 것이 현재 옵션이 아닙니다.


답변 해 주셔서 감사합니다. 내부적으로 논의 된 몇 가지 사항을 제기합니다. 불행히도,이 중 일부는 몇 가지 이유로 우리에게 효과가 없습니다. 우리의 데이터베이스는 꽤 오래되고 다소 취하기 쉽고 2005 호환성 모드에서 실행되므로 SEQUENCES가 종료됩니다. 앱 데이터 푸시는 서비스 브로커 큐에서 새 레코드를 가져 와서 여러 스레드를 통해 푸시하는 데이터로드 도구를 통해 발생합니다. IDENTITY_INSERT는 세션 당 하나의 테이블에만 사용할 수 있으며 현재의 생각은 아키텍처를 크게 변경하지 않고도이를 충족시킬 수 없다는 것입니다. 나는 당신의 주먹 제안을 지금 테스트하고 있습니다.
Mr Moose

@MrMoose 그래, 끝에 시퀀스에 대한 자세한 정보를 포함하도록 답변을 업데이트했습니다. 어쨌든 귀하의 상황에서는 작동하지 않습니다. 그리고와의 동시성 문제에 대해 궁금해 IDENTITY_INSERT했지만 테스트하지 않았습니다. 옵션 # 1이 전반적인 문제를 해결할지 확신 할 수없는 것은 불필요한 복잡성을 줄이는 관찰 일뿐입니다. 그래도 새 "외부"ID를 삽입하는 여러 스레드가있는 경우 고유한지 어떻게 확인합니까?
솔로몬 루츠 키

@MrMoose 실제로 " IDENTITY_INSERT는 세션 당 하나의 테이블에만 사용할 수 있습니다 "와 관련하여 정확히 무엇이 문제입니까? 1) 한 번에 하나의 테이블에만 삽입 할 수 있으므로 TableB에 삽입하기 전에 TableA에 대해 테이블을 끌 수 있습니다 .2) 방금 테스트 한 결과 내 생각과 반대되는 동시성 문제는 없습니다. IDENTITY_INSERT ON두 세션에서 동일한 테이블을 가지고 문제없이 두 테이블에 삽입했습니다.
솔로몬 루츠 키

1
제안한대로 1 변경은 거의 차이가 없었습니다. 우리가 사용할 ID는 현재 데이터베이스 외부에 할당되어 레코드를 연관시키는 데 사용됩니다. 세션에 대한 나의 이해가 옳지 않아서 IDENTITY_INSERT가 작동 할 수도 있습니다. 그래도 조사하는 데 약간의 시간이 걸리므로 잠시 동안 다시 신고 할 수 없습니다. 입력 해 주셔서 다시 한 번 감사드립니다. 대단히 감사합니다.
Mr Moose

1
기존 앱의 시드 값이 높은 IDENTITY_INSERT 사용에 대한 제안이 효과적이라고 생각합니다. Aaron Bertrand는 여기 에서 동시성으로 테스트하는 좋은 예를 제시했습니다. ID 값을 지정해야하는 테이블을 처리 할 수 ​​있도록 데이터로드 도구를 수정했으며 앞으로 몇 주 안에 추가 테스트를 수행 할 예정입니다.
Mr Moose
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.