Postgresには7,801,611行の2.2 GBのテーブルがあります。uuid / guid列を追加していますが、その列にデータを入力する最良の方法は何ですか(NOT NULL
制約を追加したいので)。
Postgresを正しく理解している場合、更新は技術的には削除と挿入であるため、これは基本的に2.2 gbテーブル全体を再構築しています。また、スレーブが実行されているため、遅れることを望みません。
時間をかけてゆっくりと入力するスクリプトを書くよりも良い方法はありますか?
Postgresには7,801,611行の2.2 GBのテーブルがあります。uuid / guid列を追加していますが、その列にデータを入力する最良の方法は何ですか(NOT NULL
制約を追加したいので)。
Postgresを正しく理解している場合、更新は技術的には削除と挿入であるため、これは基本的に2.2 gbテーブル全体を再構築しています。また、スレーブが実行されているため、遅れることを望みません。
時間をかけてゆっくりと入力するスクリプトを書くよりも良い方法はありますか?
回答:
要件の詳細に大きく依存します。
場合あなたが持っている十分な空き容量(の少なくとも110% pg_size_pretty((pg_total_relation_size(tbl))
、ディスク上の)と余裕があるいくつかの時間のための共有ロックと非常に短い時間のために排他ロックをし、作成新しいテーブルを含むuuid
使用して列をCREATE TABLE AS
。どうして?
以下のコードは、追加uuid-oss
モジュールの関数を使用しています。
SHARE
モードの同時変更に対してテーブルをロックします(同時読み取りを許可します)。テーブルへの書き込み試行は待機し、最終的に失敗します。下記参照。
テーブル全体をコピーし、その場で新しい列にデータを追加します-行を並べ替える可能性があります。
場合はリオーダー行しようとしている、設定してくださいwork_mem
あなたが(ない世界的に、ちょうどあなたのセッションのために)余裕ができるように高いとして。
次に、制約、外部キー、インデックス、トリガーなどを新しいテーブルに追加します。テーブルの大部分を更新する場合、行を繰り返し追加するよりもゼロからインデックスを作成する方がはるかに高速です。
新しいテーブルの準備ができたら、古いテーブルを削除し、新しいテーブルの名前を変更して、ドロップインの代替品にします。この最後のステップのみが、残りのトランザクションのために古いテーブルの排他ロックを取得します-これは非常に短いはずです。
また、テーブルの種類(ビュー、署名でテーブルの種類を使用する関数など)に応じてオブジェクトを削除し、後でそれらを再作成する必要があります。
不完全な状態を避けるために、すべてを1つのトランザクションで行います。
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
より詳細に議論する関連回答:
一時テーブルに新しい状態を準備しTRUNCATE
、元のテーブルと一時テーブルから補充します。すべて1つのトランザクションで。同時書き込みが失われないように、新しいテーブルを準備する前にSHARE
ロック を取得する必要があります。
SOに関するこれらの関連する回答の詳細:
LOCK
までとは除きますDROP
。私はワイルドで無駄な推測しか口にすることができませんでした。2.については、私の回答の補遺を考慮してください。
「ベスト」の答えはありませんが、合理的に高速に処理できる「最低の悪い」答えがあります。
私のテーブルには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するテーブルは数十個あります。非常に多くのテーブルで外部キーを切り替えることを避けたかった。そして、ビューがあります。
ドキュメント、ケーススタディ、StackOverflowを少し検索すると、「A-Ha!」が見つかりました。瞬間。ドレインはコアのUPDATEではなく、すべてのINDEX操作にありました。私のテーブルには12個のインデックスがありました-ユニーク制約のためのいくつか、クエリプランナの高速化のためのいくつか、全文検索のためのいくつか。
更新されたすべての行は、DELETE / INSERTだけでなく、各インデックスを変更して制約をチェックするオーバーヘッドも処理していました。
私の解決策は、すべてのインデックスと制約を削除し、テーブルを更新してから、すべてのインデックス/制約を再び追加することでした。
次のことを行うSQLトランザクションを記述するのに約3分かかりました。
スクリプトの実行には7分かかりました。
受け入れられた答えは間違いなくより適切で適切です...そして、ダウンタイムの必要性を事実上排除します。しかし、私の場合、そのソリューションを使用するには「開発者」の作業が大幅に必要であり、30分間のスケジュールされたダウンタイムを達成できました。ソリューションは10で対処しました。
ALTER TABLE .. ADD COLUMN ...
ことがありますか、それとも回答する必要がありますか?