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


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.