요구 사항의 세부 사항에 따라 크게 다릅니다.
경우 당신이 충분한 여유 공간 (적어도 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는 어디에 있습니까 ? 예외를 피하고 앱 코드에서 쿼리를 다시 시도하여 예외를 피해야합니다.
그런 일을 감당할 수 없다면, 원래의 테이블 을 유지 해야합니다.
기존 테이블을 유지하는 두 가지 대안
NOT NULL
제약 조건 을 추가하기 전에 업데이트 (한 번에 작은 세그먼트에서 업데이트 실행 가능) NULL 값과 NOT NULL
제한 없이 새 열을 추가하는 것이 저렴합니다.
Postgres 9.2 부터 다음을 사용하여 CHECK
제약 조건을NOT VALID
만들 수도 있습니다 .
후속 삽입 또는 업데이트에 대해서는 여전히 구속 조건이 적용됩니다.
즉 업데이트 행을 수행 할 수 있습니다 PEU à PEU 에 - 여러 별도의 거래 . 이렇게하면 행 잠금을 너무 오래 유지하지 않아도되고 죽은 행을 재사용 할 수도 있습니다. VACUUM
autovacuum이 시작될 시간이 충분하지 않으면 수동으로 실행 해야합니다. 마지막으로 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
더 자세히 논의 하는 관련 답변 :
A의 새로운 상태를 준비 임시 테이블 , TRUNCATE
원본과 리필 임시 테이블에서. 하나의 거래 에서 모두 . 동시 쓰기 손실을 방지하기 위해 새 테이블을 준비 하기 전에 여전히 SHARE
잠금 을 수행해야합니다 .
SO에 대한 이러한 관련 답변의 세부 사항 :
ALTER TABLE .. ADD COLUMN ...
아니면 그 부분도 대답해야합니까?