2016-11-23 2 views
2

자주 (자주 읽지 만 드물게 작성된) 대형 (20M 행) 열을 백필로 채우고 싶습니다. 다양한 articlesquestions on SO에서이 작업을 수행하는 가장 좋은 방법은 구조가 동일한 테이블을 만들고 백필 데이터에로드 한 다음 라이브 스왑 (이름 바꾸기가 매우 빠르기 때문에)과 같은 것입니다. 좋은 소리!Postgres에서 테이블 스와핑이 너무 장황한 이유는 무엇입니까?

그러나 실제로이 작업을 수행하는 스크립트를 작성하면 이 마침내입니다. 여기에 맛이 있습니다 :

BEGIN; 
    CREATE TABLE foo_new (LIKE foo); 
    -- I don't use INCLUDING ALL, because that produces Indexes/Constraints with different names 

    -- This is the only part of the script that is specific to my case. 
    -- Everything else is standard for any table swap 
    INSERT INTO foo_new (id, first_name, last_name, email, full_name) 
    (SELECT id, first_name, last_name, email, first_name || last_name) FROM foo); 

    CREATE SEQUENCE foo_new_id_seq 
    START 1 
    INCREMENT BY 1 
    NO MINVALUE 
    NO MAXVALUE 
    CACHE 1; 
    SELECT setval('foo_new_id_seq', COALESCE((SELECT MAX(id)+1 FROM foo_new), 1), false); 
    ALTER SEQUENCE foo_new_id_seq OWNED BY foo_new.id; 
    ALTER TABLE ONLY foo_new ALTER COLUMN id SET DEFAULT nextval('foo_new_id_seq'::regclass); 
    ALTER TABLE foo_new 
    ADD CONSTRAINT foo_new_pkey 
    PRIMARY KEY (id); 
COMMIT; 

-- Indexes are made concurrently, otherwise they would block reads for 
-- a long time. Concurrent index creation cannot occur within a transaction. 
CREATE INDEX CONCURRENTLY foo_new_on_first_name ON foo_new USING btree (first_name); 
CREATE INDEX CONCURRENTLY foo_new_on_last_name ON foo_new USING btree (last_name); 
CREATE INDEX CONCURRENTLY foo_new_on_email ON foo_new USING btree (email); 
-- One more line for each index 

BEGIN; 
    ALTER TABLE foo RENAME TO foo_old; 
    ALTER TABLE foo_new RENAME TO foo; 

    ALTER SEQUENCE foo_id_seq RENAME TO foo_old_id_seq; 
    ALTER SEQUENCE foo_new_id_seq RENAME TO foo_id_seq; 

    ALTER TABLE foo_old RENAME CONSTRAINT foo_pkey TO foo_old_pkey; 
    ALTER TABLE foo RENAME CONSTRAINT foo_new_pkey TO foo_pkey; 

    ALTER INDEX foo_on_first_name RENAME TO foo_old_on_first_name; 
    ALTER INDEX foo_on_last_name RENAME TO foo_old_on_last_name; 
    ALTER INDEX foo_on_email RENAME TO foo_old_on_email; 
    -- One more line for each index 

    ALTER INDEX foo_new_on_first_name RENAME TO foo_on_first_name; 
    ALTER INDEX foo_new_on_last_name RENAME TO foo_on_last_name; 
    ALTER INDEX foo_new_on_email RENAME TO foo_on_email; 
    -- One more line for each index 
COMMIT; 

-- TODO: drop old table (CASCADE) 

그리고 이것은 외래 키나 다른 제약을 포함하지 않습니다! INSERT INTO 비트의 내 사례에만 해당되는 부분이 있기 때문에 이러한 스와핑 작업을 수행하는 내장 된 Postgres 함수가 없다는 사실에 놀랐습니다. 이 작업이 내가하는 것보다 덜 일반적입니까? 이것이 달성 될 수있는 다양한 방법을 과소 평가하고 있습니까? 이름을 일관되게 비정규 적으로 유지하겠습니까?

+0

올바른 정의로 ​​두 테이블을 유지 한 다음 뷰를 사용하여 "활성"테이블에서 데이터를 가져옵니다. 끊임없이 테이블을 만들고 삭제할 필요가 없습니다. 간단한 'truncate ... restart identity'는 목표 테이블을 지우기에 충분합니다. –

+0

나는이 아이디어가 마음에 든다. 그리고 내가 자주 한 테이블을 대량 업데이트한다면 분명히 도움이된다. 그러나 우리는 [보기에 외래 키를 정의 할 수 없습니다] (https://www.postgresql.org/message-id/[email protected]), 그래서 우리는 이들을 모두 바꿀 필요가 있습니다. 시각. 그리고 매번 다른 테이블을 대량 업데이트한다면이 점이 도움이되지 않습니다. –

+0

FK 제한이 문제입니다. 동의합니다. 자주해야 할 일이 있다면, 동적 SQL을 사용하는 모든 기능을 만들 것입니다. –

답변

2

아마도 그렇게 일반적인 것은 아닙니다. 대부분의 테이블은 보증하기에 충분하지 않으며 대부분의 응용 프로그램은 여기 저기에 어느 정도의 가동 중지 시간을 허용 할 수 있습니다.

다른 응용 프로그램은 작업 부하에 따라 다양한 방법으로 모서리를자를 수 있습니다. 데이터베이스 서버는 할 수 없습니다. 가능한 모든 모호한 edge-case를 처리해야합니다 (또는 매우 의도적으로 이 아닌 핸들). 예상보다 훨씬 어려울 수 있습니다. 궁극적으로 다양한 유스 케이스에 맞게 맞춤형 솔루션을 작성하는 것이 더 바람직합니다. 실제 사건은 이러한 노력의 모든 수도 여전히 더 복잡이라고 가정

ALTER TABLE foo RENAME TO foo_base; 
CREATE VIEW foo AS 
    SELECT 
    id, 
    first_name, 
    last_name, 
    email, 
    (first_name || last_name) AS full_name 
    FROM foo_base; 

: 당신은 그냥 first_name || last_name으로 계산 필드를 구현하려는 경우

어쨌든, 더 나은 그것을하는 방법이 있습니다 불필요한. 복사 및 이름 바꾸기 접근 방식은이 프로세스 기간 동안 동시 수정에 대해 테이블을 잠글 필요가 있다는 가정하에 이루어 지므로 가능한 한 빨리 완료하는 것이 목표입니다. 모든 동시 작업이 읽기 전용 인 경우 (테이블을 잠그지 않았기 때문에) - UPDATE (이는 SELECT을 차단하지 않음)으로 처리하는 것이 더 나을 것입니다. 조금 더 오래 걸릴 수 있습니다 (외래 키 다시 검사 및 TOAST 테이블 재 작성을 피할 수있는 이점이 있지만). 이 방법은 정말 정당화 경우

, 나는 개선을위한 몇 가지 기회가 생각 :

  • 당신은 순서를 다시 설정/다시 할 필요가 없습니다; 기존 시퀀스를 새 테이블에 연결할 수 있습니다.
  • CREATE INDEX CONCURRENTLY은 아직 다른 사람이 foo_new에 액세스하려고 시도하지 않아야하므로 불필요한 것처럼 보입니다. 사실 전체 스크립트가 하나의 트랜잭션에 있다면이 시점에서 외부에서 볼 수 없습니다.
  • 테이블 이름은 스키마 내에서만 고유해야합니다. 새 테이블에 대한 스키마를 임시로 만드는 경우 해당 RENAME을 모두 ALTER TABLE foo SET SCHEMA public으로 바꿀 수 있어야합니다.
  • 동시 쓰기가 예상되지 않더라도 어쨌든 LOCK foo IN SHARE MODE은 아프지 않을 것입니다 ...

편집 : 그들이 자신의 부모 테이블과 같은 스키마에 머물 필요가 있다고 보인다

시퀀스 재 할당은, 내가 기대했던 것보다 조금 더 복잡합니다. 그러나 여기에 실제적인 예가 있습니다 :

BEGIN; 
    LOCK public.foo IN SHARE MODE; 
    CREATE SCHEMA tmp; 
    CREATE TABLE tmp.foo (LIKE public.foo); 

    INSERT INTO tmp.foo (id, first_name, last_name, email, full_name) 
    SELECT id, first_name, last_name, email, (first_name || last_name) FROM public.foo; 

    ALTER TABLE tmp.foo ADD CONSTRAINT foo_pkey PRIMARY KEY (id); 
    CREATE INDEX foo_on_first_name ON tmp.foo (first_name); 
    CREATE INDEX foo_on_last_name ON tmp.foo (last_name); 
    CREATE INDEX foo_on_email ON tmp.foo (email); 
    ALTER TABLE tmp.foo ALTER COLUMN id SET DEFAULT nextval('public.foo_id_seq'); 

    ALTER SEQUENCE public.foo_id_seq OWNED BY NONE; 
    DROP TABLE public.foo; 

    ALTER TABLE tmp.foo SET SCHEMA public; 
    ALTER SEQUENCE public.foo_id_seq OWNED BY public.foo.id; 
    DROP SCHEMA tmp; 
COMMIT; 
+0

데이터베이스의 모든 테이블을 읽는 재 작업이 있기 때문에'CREATE INDEX CONCURRENTLY'를 사용했습니다. 오히려 오랫동안 그것을 막지 않을 것입니다. 나는 당신의 스키마 아이디어를 좋아한다. 테이블의 스키마를 변경하면 해당 인덱스의 스키마도 변경됩니까? –

+0

좋은 답변입니다. 당신 말이 맞아요, 나는 동시 읽기에 대해서만 걱정하고 있습니다. 그래서 나는 아마 포기하고'UPDATE '를 사용할 것입니다. 하지만 나중에이 아이디어를 모두 버리도록하겠습니다. –

+0

@ Simon : 예, 인덱스가 테이블과 함께 이동합니다. 'DROP SCHEMA'는 스키마가 비어있는 경우에만 성공할 것이므로, 뒤에 아무것도 남기지 않았 음을 확신 할 수 있습니다. –