PostgreSQLバックアップなしでClogリカバリを誤って削除

43108 ワード

実験テーブルの作成
postgres# create table t (n_id int primary key,c_name varchar(300));
CREATE TABLE
postgres# insert into t select id,(id*1000)::text as name from
generate_series(1,1000) id;
INSERT 0 1000
postgres# delete from t where n_id =1000;
DELETE 1
postgres# update t set c_name = 'cs' where n_id > 990;
UPDATE 9
postgres# select * from t; --
postgres# insert into t values ( 1001,'insert'),(1002,'insert');
INSERT 0 2
postgres# update t set c_name = 'update' where n_id = 1002;
UPDATE 1

 
データベース$pg_を閉じてバックアップctl stopはサーバプロセスが閉じるのを待つ....完了サーバプロセスは$cp-R$PGDATA$PGDATA/../をオフにしました.pgdata_bak1
 
clogファイル$cd$PGDATA/pg_を削除xact$ ls0000 bak$ rm 0000
データベースエラー$pg_の起動ctl startpg_ctl:サーバプロセスチェックログ出力を起動できません.
いくつかの重要でない出力を省略して、pgを開くことができません.xact/0000ファイル.
$ tail postgresql-2020-08-11_123341.csv授権失効処理方式:通知近失効授権注意日数:15",,,,,,,,,,,,"""
ddコマンドを使用してclogファイルを作成します.clogファイルは最大256 Kなので、256 Kのファイルを作成するだけです.すべてのトランザクションがIN_にあることを示す全0のファイルを書き込みます.PROGRESSステータス.
ステータスID取引ステータス
0x00 TRANSACTION_STATUS_IN_PROGRESS
0x01 TRANSACTION_STATUS_COMMITTED
0x02 TRANSACTION_STATUS_ABORTED
0x03 TRANSACTION_STATUS_SUB_COMMITTED

$ dd if=/dev/zero of=$PGDATA/pg_xact/0000 bs=8K co

 
unt=32
32+0を記録した読み込み32+0を記録した書き出し262144バイト(262 kB)がコピーされ、0.00102351秒、256 MB/秒
再起動、データ損失の検証
postgres# select * from t where n_id >=990;
n_id | c_name
------+--------
990 | 990000
991 | cs
992 | cs
993 | cs
994 | cs
995 | cs
996 | cs
997 | cs
998 | cs
999 | cs
1002 | insert

 
clogが失われ、一部のトランザクションの影響は依然として保持されています.
IN_PROGRESSステータスのトランザクションによるデータの操作は、他のセッションでは表示されないはずです.資料を見ると、通常のデータメタグループは3つの部分から構成されており、HeapTupleHeaderData構造、空のビットマップ、およびユーザーデータであることがわかります.HeapTupleHeaderDataの構造は以下の通りで、ソース
Field Type Length Description
t_xmin TransactionId 4 bytes insert XID stamp
t_xmax TransactionId 4 bytes delete XID stamp
t_cid CommandId 4 bytes insert and/or delete CID stamp (overlays
with t_xvac)
t_xvac TransactionId 4 bytes XID for VACUUM operation moving a row
version
t_ctid ItemPointerData 6 bytes current TID of this or newer row version
t_infomask2 uint16 2 bytes number of attributes, plus various flag bits
t_infomask uint16 2 bytes various flag bits
t_hoff uint8 1 byte offset to user data

 
ここでt_infomaskも行の可視性を決定し、t_infomaskの優先度はもっと高いです.全部で16ビットのバイナリで、4ビットごとに1つの意味を表します.2番目のセグメントは、行の可視性を判断するために使用されます.
#define HEAP_HASNULL 0x0001 /* has null attribute(s) */
#define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */
#define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */
#define HEAP_HASOID 0x0008 /* has an object-id field */
#define HEAP_XMAX_KEYSHR_LOCK 0x0010 /* xmax is a key-shared locker */
#define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */
#define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */
#define HEAP_XMAX_LOCK_ONLY 0x0080 /* xmax, if valid, is only a locker */
/* xmax is a shared locker */
#define HEAP_XMAX_SHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
#define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */
#define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
#define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */
#define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */
#define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */
#define HEAP_UPDATED 0x2000 /* this is UPDATEd version of row */
#define HEAP_MOVED_OFF 0x4000 /* moved to another place by pre-9.0
* VACUUM FULL; kept for binary * upgrade
support */
#define HEAP_MOVED_IN 0x8000 /* moved from another place by pre-9.0
* VACUUM FULL; kept for binary * upgrade support
*/
#define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
#define HEAP_XACT_MASK 0xFFF0 /* visibility-related bits */

 
さっきの実験の状況から見ると、私たちは大胆に推測した:t_infomaskの更新時間は、データ書き込み後の初回アクセスです.そこでidが1002のデータ更新時には、1002の挿入操作のコミット情報をt_に書き込むinfomaskフィールド.したがって、上記の実験は1001への挿入と1002の更新のみを失う.
ソース検証の表示コメントから分かるように、clog(10以上xactと改名)ログの競合を避けるために、コードは以前のすぐにt_を更新するinfomask、初回アクセス時に変更(postgres 9.6バージョン、abase 3.6.1バージョン対応)heapam_visibility.c 962行
 
/*
* HeapTupleSatisfiesMVCC
* True iff heap tuple is valid for the given MVCC snapshot.
* *
See SNAPSHOT_MVCC's definition for the intended behaviour.
* *
Notice that here, we will not update the tuple status hint bits if the
* inserting/deleting transaction is still running according to our snapshot,
* even if in reality it's committed or aborted by now. This is intentional.
* Checking the true transaction state would require access to high-traffic
* shared data structures, creating contention we'd rather do without, and it
* would not change the result of our visibility check anyway. The hint bits
* will be updated by the first visitor that has a snapshot new enough to see
* the inserting/deleting transaction as done. In the meantime, the cost of
* leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
* call will need to run TransactionIdIsCurrentTransactionId in addition to
* XidInMVCCSnapshot (but it would have to do the latter anyway). In the old
* coding where we tried to set the hint bits as soon as possible, we instead
* did TransactionIdIsInProgress in each call --- to no avail, as long as the
* inserting/deleting transaction was still running --- which was more cycles
* and more contention on the PGXACT array.
*/
static bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer)
{
HeapTupleHeader tuple = htup->t_data;
Assert(ItemPointerIsValid(&htup->t_self));
Assert(htup->t_tableOid != InvalidOid);
if (!HeapTupleHeaderXminCommitted(tuple))
{
if (HeapTupleHeaderXminInvalid(tuple))
return false;
/* Used by pre-9.0 binary upgrades */
if (tuple->t_infomask & HEAP_MOVED_OFF)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (TransactionIdIsCurrentTransactionId(xvac))
return false;
if (!XidInMVCCSnapshot(xvac, snapshot))
{if (TransactionIdDidCommit(xvac))
{
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
InvalidTransactionId);
return false;
} S
etHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
InvalidTransactionId);
}
} /
* Used by pre-9.0 binary upgrades */
else if (tuple->t_infomask & HEAP_MOVED_IN)
{
TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
if (!TransactionIdIsCurrentTransactionId(xvac))
{
if (XidInMVCCSnapshot(xvac, snapshot))
return false;
if (TransactionIdDidCommit(xvac))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
InvalidTransactionId);
else
{
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
InvalidTransactionId);
return false;
}
}
} e
lse if
(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
{
if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
return false; /* inserted after scan started */
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter
*/
return true;
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
xmax = HeapTupleGetUpdateXid(tuple);
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* updated after scan started */
elseheapam_visibility.c 113 
return false; /* updated before scan started */
} i
f
(!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
return true;
} i
f (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
} e
lse if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
return false;
else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
HeapTupleHeaderGetRawXmin(tuple));


heapam_visibility.c 113 

/*
* SetHintBits()
* *
Set commit/abort hint bits on a tuple, if appropriate at this time.
* *
It is only safe to set a transaction-committed hint bit if we know the
* transaction's commit record is guaranteed to be flushed to disk before the
* buffer, or if the table is temporary or unlogged and will be obliterated by
* a crash anyway. We cannot change the LSN of the page here, because we may
* hold only a share lock on the buffer, so we can only use the LSN to
* interlock this if the buffer's LSN already is newer than the commit LSN;
* otherwise we have to just refrain from setting the hint bit until some
* future re-examination of the tuple.
* *
We can always set hint bits when marking a transaction aborted. (Some
* code in heapam.c relies on that!)
* *
Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then
* we can always set the hint bits, since pre-9.0 VACUUM FULL always used
* synchronous commits and didn't move tuples that weren't previously
* hinted. (This is not known by this subroutine, but is applied by its
* callers.) Note: old-style VACUUM FULL is gone, but we have to keep this
* module's support for MOVED_OFF/MOVED_IN flag bits for as long as we
* support in-place update from pre-9.0 databases.
* *
Normal commits may be asynchronous, so for those we need to get the LSN
* of the transaction and then check whether this is flushed.
* *
The caller should pass xid as the XID of the transaction to check, or
* InvalidTransactionId if no check is needed.
*/
static inline void
SetHintBits(HeapTupleHeader tuple, Buffer buffer,
uint16 infomask, TransactionId xid)
{
if (TransactionIdIsValid(xid))
{
/* NB: xid must be known committed here! */
XLogRecPtr commitLSN = TransactionIdGetCommitLSN(xid);
if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) &&
BufferGetLSNAtomic(buffer) < commitLSN)
{
/* not flushed and no LSN interlock, so don't set hint */
return;
}
} t
uple->t_infomask |= infomask;
MarkBufferDirtyHint(buffer, true);
}

 
データバックアップをまとめることが重要です.行の可視性判断は、この行のxmin、xmax、clogの決定に加えて、行のt_infomaskも行の可視性を決定します.優先度はclogより高い.トランザクションが変更されてコミットされると、コミットステータスはすぐにt_に書き込まれません.infomaskフィールドは、レコードが最初にアクセスされたときに書き込まれる必要があります(abase 3.6.1以降).clogが失われた場合、dd 1つの全0ファイルでデータベースを起動すると、一部のデータが失われる(挿入後にアクセスせず、挿入が失われる;削除後にアクセスせず、データは依然として削除されていない;更新後にアクセスせず、古いデータが表示され、更新の変更が失われる).
データベースバックアップなし、可視性の原則を誤って削除してリカバリ注意:特定の状況でのリカバリのみをサポートし、データバックアップをしっかりと行わなければならないことを強調し、バックアップを利用してデータの安全を保障する.この例では、mvcc、vacuum、および記録可視性ルールを理解するのに便利です.実験テーブルを作成してすべてのレコードを削除し、シミュレーション誤削除autovacuumを開いた状態でテーブルのすべてのデータを削除し、autovacuumプロセスを実行すると、テーブルのすべてのデータがクリーンアップされます.この場合、データはこのメソッドで復元できないため、削除後はできるだけ早くデータベースを閉じます.
Autovacuumをoffvi$PGDATA/postgresqlに変更します.auto.conf
#autovacuum='off'の構成を変更または追加
データベースを起動して削除コマンドのトランザクションidを表示
この実験では、表を新しく作成し、簡単に見えます.削除されたトランザクションidは1つのみ、253975です.get_raw_Page関数の最初のパラメータはテーブル名で、2番目のパラメータはpageの番号で、0から始まります.
select t_xmax,* from heap_page_items(get_raw_page('public.t',0));
select t_xmax,* from heap_page_items(get_raw_page('public.t',1));
select t_xmax,* from heap_page_items(get_raw_page('public.t',2));
select t_xmax,* from heap_page_items(get_raw_page('public.t',3));
select t_xmax,* from heap_page_items(get_raw_page('public.t',4));
select t_xmax,* from heap_page_items(get_raw_page('public.t',5));
t_xmax | lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid
| t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
--------+-----+--------+----------+--------+--------+--------+----------+-------
--+-------------+------------+--------+--------+-------+------------------------
--
253975 | 1 | 8152 | 1 | 33 | 253974 | 253975 | 0 | (0,1)
| 8194 | 258 | 24 | | | \x010000000b31303030
253975 | 2 | 8112 | 1 | 33 | 253974 | 253975 | 0 | (0,2)
| 8194 | 258 | 24 | | | \x020000000b32303030
253975 | 3 | 8072 | 1 | 33 | 253974 | 253975 | 0 | (0,3)
| 8194 | 258 | 24 | | | \x030000000b33303030
253975 | 4 | 8032 | 1 | 33 | 253974 | 253975 | 0 | (0,4)
| 8194 | 258 | 24 | | | \x040000000b34303030
253975 | 5 | 7992 | 1 | 33 | 253974 | 253975 | 0 | (0,5)
| 8194 | 258 | 24 | | | \x050000000b35303030
253975 | 6 | 7952 | 1 | 33 | 253974 | 253975 | 0 | (0,6)
| 8194 | 258 | 24 | | | \x060000000b36303030
253975 | 7 | 7912 | 1 | 33 | 253974 | 253975 | 0 | (0,7)
| 8194 | 258 | 24 | | | \x070000000b37303030
253975 | 8 | 7872 | 1 | 33 | 253974 | 253975 | 0 | (0,8)
| 8194 | 258 | 24 | | | \x080000000b38303030

 
データベース$pg_を閉じるctl stopはサーバプロセスが閉じるのを待つ....完了サーバープロセスが停止しました
pg_resetwal次のトランザクションidを変更する前に調べた誤削除トランザクションid
$ pg_resetwal $PGDATA -x 253975Write-ahead log reset
データベース$pg_の起動ctl startサーバプロセスが開始されました
エラー削除データをテンポラリ・テーブルの現在のデータベースにバックアップする次のトランザクションidは253975であるため、トランザクションidが253975のエラー削除は表示されません.テーブル内では現在も誤削除された1000件のデータがクエリーされており、これらのデータのxmaxは253975である.
postgres# create table t_del as select * from t where xmax=253975;

 
tテーブルを再クエリすると、前のテーブル文でトランザクションidが1つ大きくなったため、データは表示されません.誤削除トランザクションによるデータの変更が表示されます.
postgres# select * from t;
n_id | c_name
------+--------
(0  )

 
データをtテーブルに挿入し、データ復旧を完了する
postgres# insert into t select * from t_del;
INSERT 0 1000
postgres# select count(*) from t;
count
-------
1000
(1  )

 
sqlを実行して変更を有効にするselect pg_reload_conf();
データバックアップをまとめることが重要です.abaseのvacuumメカニズムのため、削除したデータは、すぐに削除されません.関連する標識を作っただけです.vacuumがこれらのデータをクリーンアップすると、リカバリできません.Autovacuumの最低実行間隔はautovacuum_naptimeパラメータ制御、デフォルト1分.Autovacuumを実行するときに、テーブルに対してvacuumを行うかどうか、autovacuum_vacuum_scale_factorパラメータおよびautovacuum_vacuum_thresholdパラメータは共に決定され、dead tupleの数>=autovacuum_を同時に満たすだけである.vacuum_scale_factor*reltuples(表のレコード数)+autovacuum_vacuum_thresholdは、テーブルに対してvacuum操作を行います.
postgres# show autovacuum_naptime ;
autovacuum_naptime
--------------------
1min
postgres# show autovacuum_vacuum_scale_factor ;
autovacuum_vacuum_scale_factor
--------------------------------
0.2
(1  )
postgres# show autovacuum_vacuum_threshold ;
autovacuum_vacuum_threshold
-----------------------------
50