큰 테이블에 새 열을 채우는 가장 좋은 방법은 무엇입니까?


33

Postgres에는 2.2GB 테이블이 7,801,611 개 있습니다. 우리는 UUID / GUID 열을 추가하고 있으며 그 열을 채우는 가장 좋은 방법이 무엇인지 궁금합니다 NOT NULL.

Postgres를 올바르게 이해하면 업데이트는 기술적으로 삭제 및 삽입이므로 기본적으로 전체 2.2GB 테이블을 다시 작성합니다. 또한 우리는 슬레이브가 작동하므로 지연되는 것을 원하지 않습니다.

시간이지나면서 천천히 채워지는 스크립트를 작성하는 것보다 더 좋은 방법이 있습니까?


2
이미 실행 했습니까? ALTER TABLE .. ADD COLUMN ...아니면 그 부분도 대답해야합니까?
ypercubeᵀᴹ

계획 단계에서 아직 테이블 수정을 실행하지 않았습니다. 열을 추가하고 채우고 제약 조건이나 인덱스를 추가 하여이 작업을 수행했습니다. 그러나이 테이블은 상당히 커져서로드, 잠금, 복제 등에 대해 걱정하고 있습니다.
Collin Peters

답변:


45

요구 사항의 세부 사항에 따라 크게 다릅니다.

경우 당신이 충분한 여유 공간 (적어도 110 % pg_size_pretty((pg_total_relation_size(tbl))디스크)를하고 감당할 수있는 시간에 대한 공유 잠금매우 짧은 시간에 대한 배타적 잠금을 , 다음 만들 새 테이블 을 포함 uuid하여 열을 CREATE TABLE AS. 왜?

아래 코드는 추가 uuid-oss모듈함수를 사용 합니다 .

  • SHARE모드의 동시 변경에 대해 테이블을 잠급니다 (여전히 읽기를 허용 함). 테이블에 쓰려고 시도하면 대기하고 결국 실패합니다. 아래를 참조하십시오.

  • 새 열을 즉시 채우면서 전체 테이블을 복사하십시오. 행에있는 동안 유리하게 행을 정렬 할 수 있습니다. 행을 재정렬하려는
    경우work_mem 가능한 한 높게 설정하십시오 (전역이 아닌 세션에 대해서만).

  • 그런 다음 제약 조건, 외래 키, 인덱스, 트리거 등을 새 테이블에 추가하십시오. 테이블의 많은 부분을 업데이트 할 때 반복적으로 행을 추가하는 것보다 처음부터 인덱스를 만드는 것이 훨씬 빠릅니다.

  • 새 테이블이 준비되면 이전 테이블을 삭제하고 새 이름을 바꾸어 대체 대체물로 만듭니다. 이 마지막 단계 만 나머지 트랜잭션에 대해 이전 테이블에 대한 독점 잠금을 얻습니다. 지금은 매우 짧아야합니다.
    또한 테이블 유형 (뷰, 서명에서 테이블 유형을 사용하는 함수 등)에 따라 객체를 삭제 한 후 나중에 다시 작성해야합니다.

  • 불완전한 상태를 피하려면 한 번의 트랜잭션으로 모두 수행하십시오.

BEGIN;
LOCK TABLE tbl IN SHARE MODE;

SET LOCAL work_mem = '???? MB';  -- just for this transaction

CREATE TABLE tbl_new AS 
SELECT uuid_generate_v1() AS tbl_uuid, <list of all columns in order>
FROM   tbl
ORDER  BY ??;  -- optionally order rows favorably while being at it.

ALTER TABLE tbl_new
   ALTER COLUMN tbl_uuid SET NOT NULL
 , ALTER COLUMN tbl_uuid SET DEFAULT uuid_generate_v1()
 , ADD CONSTRAINT tbl_uuid_uni UNIQUE(tbl_uuid);

-- more constraints, indices, triggers?

DROP TABLE tbl;
ALTER TABLE tbl_new RENAME tbl;

-- recreate views etc. if any
COMMIT;

가장 빠릅니다. 다른 업데이트 방법은 전체 테이블을 더 비싼 방식으로 다시 작성해야합니다. 디스크에 충분한 여유 공간이 없거나 전체 테이블을 잠글 수 없거나 동시 쓰기 시도에 대한 오류를 생성 할 수없는 경우에만 해당 경로로 이동합니다.

동시 쓰기는 어떻게됩니까?

트랜잭션이 잠금 을 수행 한 후 동일한 테이블에서 INSERT/ UPDATE/ DELETE를 시도하는 다른 트랜잭션 (다른 세션에서) SHARE은 잠금이 해제되거나 시간 초과가 시작될 때까지 기다립니다. 그들은 것이다 실패 그들 아래에서 삭제 된에 쓸하려고 한 테이블에 있기 때문에, 어느 쪽이든.

새 테이블에 새 테이블 OID가 있지만 동시 트랜잭션이 이미 테이블 이름을 이전 테이블 의 OID로 분석했습니다 . 잠금이 최종적으로 해제되면 테이블에 쓰기 전에 테이블 자체를 잠그고 사라진 것을 찾습니다. Postgres는 다음과 같이 답변합니다 :

ERROR: could not open relation with OID 123456

123456이전 테이블의 OID는 어디에 있습니까 ? 예외를 피하고 앱 코드에서 쿼리를 다시 시도하여 예외를 피해야합니다.

그런 일을 감당할 수 없다면, 원래의 테이블 을 유지 해야합니다.

기존 테이블을 유지하는 두 가지 대안

  1. NOT NULL제약 조건 을 추가하기 전에 업데이트 (한 번에 작은 세그먼트에서 업데이트 실행 가능) NULL 값과 NOT NULL제한 없이 새 열을 추가하는 것이 저렴합니다.
    Postgres 9.2 부터 다음을 사용하여 CHECK제약 조건을NOT VALID 만들 수도 있습니다 .

    후속 삽입 또는 업데이트에 대해서는 여전히 구속 조건이 적용됩니다.

    즉 업데이트 행을 수행 할 수 있습니다 PEU à PEU 에 - 여러 별도의 거래 . 이렇게하면 행 잠금을 너무 오래 유지하지 않아도되고 죽은 행을 재사용 할 수도 있습니다. VACUUMautovacuum이 시작될 시간이 충분하지 않으면 수동으로 실행 해야합니다. 마지막으로 NOT NULL구속 조건을 추가하고 구속 조건을 제거합니다 NOT VALID CHECK.

    ALTER TABLE tbl ADD CONSTRAINT tbl_no_null CHECK (tbl_uuid IS NOT NULL) NOT VALID;
    
    -- update rows in multiple batches in separate transactions
    -- possibly run VACUUM between transactions
    
    ALTER TABLE tbl ALTER COLUMN tbl_uuid SET NOT NULL;
    ALTER TABLE tbl ALTER DROP CONSTRAINT tbl_no_null;

    NOT VALID더 자세히 논의 하는 관련 답변 :

  2. A의 새로운 상태를 준비 임시 테이블 , TRUNCATE원본과 리필 임시 테이블에서. 하나의 거래 에서 모두 . 동시 쓰기 손실을 방지하기 위해 새 테이블을 준비 하기 전에 여전히 SHARE잠금 을 수행해야합니다 .

    SO에 대한 이러한 관련 답변의 세부 사항 :


환상적인 답변! 내가 찾던 정보가 정확합니다. 두 가지 질문 1. 이와 같은 동작이 얼마나 오래 걸리는지 쉽게 테스트 할 수있는 방법이 있습니까? 2. 5 분이 걸리면 5 분 동안 해당 테이블의 행을 업데이트하려고하는 작업은 어떻게됩니까?
Collin Peters

@CollinPeters : 1. 시간의 가장 큰 비중은 큰 테이블을 복사하고 인덱스와 제약을 다시 만드는 것입니다. 삭제 및 이름 바꾸기가 저렴합니다. 테스트 LOCK하기 위해를 제외하고 준비된 SQL 스크립트를 실행할 수 있습니다 DROP. 나는 거칠고 쓸모없는 추측 만 할 수 있었다. 2는 내 대답에 대한 부록을 고려하십시오.
Erwin Brandstetter

@ErwinBrandstetter 다시보기를 다시 작성하십시오. 따라서 테이블 이름을 바꾼 후에도 여전히 오래된 테이블 (oid)을 사용하는 수십 개의 뷰가있는 경우. 전체보기 새로 고침 / 생성을 다시 실행하는 대신 깊은 교체를 수행 할 수있는 방법이 있습니까?
CodeFarmer

@CodeFarmer : 테이블의 이름 만 바꾸면 뷰는 이름이 바뀐 테이블을 계속 사용합니다. 뷰가 대신 테이블을 사용하게하려면 테이블을 기반으로 뷰 를 다시 작성해야합니다. (이전 테이블을 삭제할 수도 있습니다.) 주위에 (실제) 방법이 없습니다.
Erwin Brandstetter

14

나는 "최고의"답변이 없지만, "최소한의 나쁜"답변을 통해 합리적으로 빠른 작업을 수행 할 수 있습니다.

내 테이블에는 2MM 행이 있었고 기본으로 두 번째 타임 스탬프 열을 추가하려고 할 때 업데이트 성능이 저하되었습니다.

ALTER TABLE mytable ADD new_timestamp TIMESTAMP ;
UPDATE mytable SET new_timestamp = old_timestamp ;
ALTER TABLE mytable ALTER new_timestamp SET NOT NULL ;

40 분 동안 걸린 후, 작은 배치로이 작업을 수행하는 데 걸리는 시간에 대한 아이디어를 얻었습니다. 예상치는 약 8 시간이었습니다.

허용 된 답변이 확실히 낫지 만이 테이블은 내 데이터베이스에서 많이 사용됩니다. FKEY하는 수십 개의 테이블이 있습니다. 너무 많은 테이블에서 FOREIGN KEYS를 전환하지 않으려 고했습니다. 그리고보기가 있습니다.

약간의 문서, 사례 연구 및 StackOverflow를 검색했으며 "A-Ha!" 순간. 드레인은 핵심 UPDATE가 아니라 모든 INDEX 작업에있었습니다. 내 테이블에는 12 개의 인덱스가 있습니다. 몇 가지는 고유 제약 조건, 쿼리 플래너 속도 향상 및 전체 텍스트 검색을위한 것입니다.

UPDATED 된 모든 행은 DELETE / INSERT 작업뿐만 아니라 각 인덱스를 변경하고 제약 조건을 검사하는 오버 헤드도 수행했습니다.

내 솔루션은 모든 인덱스와 제약 조건을 삭제하고 테이블을 업데이트 한 다음 모든 인덱스 / 제약 조건을 다시 추가하는 것이 었습니다.

다음을 수행하는 SQL 트랜잭션을 작성하는 데 약 3 분이 걸렸습니다.

  • 시작;
  • 인덱스 / 컨테이너 삭제
  • 업데이트 테이블
  • 인덱스 / 제약을 다시 추가
  • 범하다;

스크립트를 실행하는 데 7 분이 걸렸습니다.

받아 들여진 대답은 확실히 더 좋고 적절합니다 ... 그리고 가동 중지 시간이 거의 필요 없습니다. 필자의 경우에는 해당 솔루션을 사용하는 데 훨씬 더 많은 "개발자"작업이 필요했으며 30 분 동안 다운 타임이 예정되어있었습니다.이 솔루션은 10 년 만에 해결되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.