私はPostgres 9.2 DBを使用していますが、特定のテーブルに削除できないデッド行がたくさんあります。
# SELECT * FROM public.pgstattuple('mytable');
table_len | Tuple_count | Tuple_len | Tuple_percent | dead_Tuple_count | dead_Tuple_len | dead_Tuple_percent | free_space | free_percent
------------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
2850512896 | 283439 | 100900882 | 3.54 | 2537195 | 2666909495 | 93.56 | 50480156 | 1.77
(1 row)
通常のバキューム処理では、削除できないデッド行が多数表示されます。
# VACUUM VERBOSE mytable;
[...]
INFO: "mytable": found 0 removable, 2404332 nonremovable row versions in 309938 out of 316307 pages
DETAIL: 2298005 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 1.90s/2.05u sec elapsed 16.79 sec.
[...]
テーブルには約300.000の実際のデータ行しかありませんが、230万のデッド行があります(これにより、特定のクエリが非常に遅くなるようです)。
による SELECT * FROM pg_stat_activity where xact_start is not null and datname = 'mydb' order by xact_start;
データベースにアクセスする古いトランザクションはありません。最も古いトランザクションは数分前のものであり、まだテーブルの何も変更していません。
私もチェックしましたselect * from pg_prepared_xacts
(準備されたトランザクションをチェックするため)およびselect * from pg_stat_replication
(保留中のレプリケーションをチェックするため)、どちらも空です。
そのテーブルで実行される挿入、更新、削除はたくさんあるので、死んだ行がたくさん作成されていることがわかります。しかし、なぜそれらはVACUUMコマンドによって削除されないのですか?
最も古いトランザクションは数分前のものであり、まだテーブルの何も変更していません。
それだけでは不十分です。これらの行をデッドとしてマークするために必要なのは、これらのトランザクションが開始されたときに、これらの行に触れた(UPDATEまたはDELETEを実行した)他のトランザクションがなかったことです。
行を更新または削除すると、以前のバージョンの行が物理的に元の場所に保持され、その xmax
フィールドに現在のトランザクションのTXIDが設定されます。他のトランザクションの観点からは、この古いバージョンの行は、スナップショットの一部である場合は引き続き表示されます。各スナップショットには、行バージョンのxmin
およびxmax
を比較できるxmin
およびxmax
があります。ポイントは、行の変更が確実にコミットされているかどうかを単にチェックするのではなく、VACUUMが行のバージョンをすべてのライブスナップショットの組み合わせた可視性と比較する必要があることです。後者は必要ですが、古いバージョンで使用されていたスペースをリサイクルするには十分ではありません。
たとえば、次のイベントのシーケンスは、それらを変更したトランザクションが終了しても、VACUUMがデッド行をクリーンアップできないようにするものです。
t0
:長時間実行トランザクションTX1が開始しますt0+30mn
:TX2が起動し、REPEATABLE READモードに設定されます。t0+35mn
:TX1が終了します。t0+40mn
:pg_stat_activityは、10分前のTX2のみを表示しますt0+45mn
:VACUUMは実行されますが、TX1によって変更された古いバージョンの行は削除されません(TX2がそれらを必要とする場合があるため)。これを再現することができました。基本的に、トランザクション内では、
READ COMMITTED
では、デフォルトのトランザクションレベル:SELECT
はAccessShareLock
を取得しますVACUUM
は不要な行のバージョンをクリーンアップできますpg_stat_activity.backend_xmin IS NULL
トランザクション用SERIALIZABLE
またはREPEATABLE READ
トランザクションレベル:SELECT
はAccessShareLock
を取得しますVACUUM
できませんできませんデッドローバージョンをクリーンアップしますpg_stat_activity.backend_xmin IS NOT NULL
トランザクション用VERBOSE
は、これらの行を「削除できない行バージョン」および「デッド行バージョン」として報告しますCREATE TABLE bar AS
SELECT x::int FROM generate_series(1,10) AS t(x);
補足として、テーブルの作成後にbar
から何かを削除すると、それらの行はremovable
になり、VACUUM
に表示されます。
INFO: "bar": removed # row versions in # pages
ここで、シナリオを再作成するためのtxnテーブルがあります。
txn1 - BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
txn1 - SELECT * FROM bar;
txn2 - DELETE FROM bar; -- We delete after the select
txn2 - VACUUM VERBOSE bar; -- Can't remove the "dead row versions"
VACUUM
はこれらの行バージョンを削除できません。SELECT * FROM bar;
の下の後続のREPEATABLE READ
が引き続きそれらを表示するためです!上記のVACUUM
は、
# VACUUM VERBOSE bar;
INFO: vacuuming "public.bar"
INFO: "bar": found 0 removable, 10 nonremovable row versions in 1 out of 1 pages
DETAIL: 10 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
これはまさにあなたが見ているものです。
VACUUM
が無効な行をクリーンアップするのを妨げているクエリを見つけるには、これを実行します。
SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity
USING (pid)
WHERE relation::regclass = 'bar'::regclass
AND granted IS TRUE
AND backend_xmin IS NOT NULL;
これはこのようなものを返します。
query │ state │ locktype │ mode
────────────────────┼─────────────────────┼──────────┼─────────────────
SELECT * FROM bar; │ idle in transaction │ relation │ AccessShareLock
それでは、TXNに戻りましょう。txn1をkill/commit/rollbackし、VACUUM
を再実行する必要があります。
txn1 - COMMIT;
txn2 - VACUUM VERBOSE bar;
そして今、私たちは、
# VACUUM VERBOSE bar;
INFO: vacuuming "public.bar"
INFO: "bar": removed 10 row versions in 1 pages
INFO: "bar": found 10 removable, 0 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO: "bar": truncated 1 to 0 pages
DETAIL: CPU 0.00s/0.00u sec elapsed 0.01 sec.
ACCESS SHARE
ロックを取得します。そして、VACUUM
はデッド行を削除できないため、「削除不可」としてマークされます。これはVACUUM VERBOSE
のかなり悪い動作だと思います。見たかったのに….
DETAIL: 10 dead row versions cannot be removed yet
could not aquire SHARE UPDATE EXCLUSIVE lock on %TABLE
VACUUM
に触れています。@ Craig Ringerによるこのコメントをまとめたもの
ただし、SERIALIZABLEまたはREPEATABLE READ xactsは、無効な行を削除してバキュームをブロックします。バキュームを続行できるのはアクティブなスナップショットを持たないREAD COMMITTED xactだけです。 WITH HOLDカーソル、準備されたxactsなど、他のいくつかのこともできます。
ウサギの穴を旅することがわかった場合 backend_xmin
の詳細についての私の質問は、この答えを使った私の仕事に触発されました
また、システムカタログを調べさせてくれた DanielVérité と、このカタログでのVACUUM
の動作に感謝します。
データベースのアクティブなトランザクションまたは特定の「foo」テーブルに対するアクティブなロックがないことを確認した後でも、この問題に直面していました。
次のメソッドは、「foo」からすべてのそれらのリムーバブルではないデッド行を正常に削除しました:
CREATE TEMP TABLE temp_foo AS SELECT * FROM "foo";
TRUNCATE TABLE "foo";
INSERT INTO "foo" SELECT * FROM temp_foo;
DROP table temp_foo;
行が多すぎる大きなテーブルがある場合、すべてのテーブル行が一時テーブルに転送されてから元のテーブルに転送されるため、これは実行可能なソリューションではない可能性があることに注意してください。