摘要:Oracle數據庫有時候不當心刪除掉數據,想查詢這些數據,或者恢復數據,就能夠使用帶有as of子句的select語句進行閃回查詢。
PG粉有福了,下面介紹一種相似「閃回查詢」插件 pg_dirtyread,能夠讀取未被vacuum的dead數據。git
github主頁:https://github.com/df7cb/pg_dirtyreadgithub
1.2 released:https://www.postgresql.org/message-id/20170923211004.uh27ncpjarkucrhd%40msg.credativ.desql
1、咱們一塊兒看下官網的3個例子:
語法:數據庫
SELECT * FROM pg_dirtyread('tablename') AS t(col1 type1, col2 type2, ...);
樣例1: 刪除找回小程序
CREATE EXTENSION pg_dirtyread; -- Create table and disable autovacuum CREATE TABLE foo (bar bigint, baz text); ALTER TABLE foo SET ( autovacuum_enabled = false, toast.autovacuum_enabled = false ); --測試方便,先把自動vacuum關閉掉。 INSERT INTO foo VALUES (1, 'Test'), (2, 'New Test'); DELETE FROM foo WHERE bar = 1; SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text); bar │ baz ─────┼────────── 1 │ Test 2 │ New Test
能夠看到, 被刪除的記錄(1, 'Test')已經能夠查詢到。數組
樣例2:列被drop的狀況架構
CREATE TABLE ab(a text, b text); INSERT INTO ab VALUES ('Hello', 'World'); ALTER TABLE ab DROP COLUMN b; DELETE FROM ab; SELECT * FROM pg_dirtyread('ab') ab(a text, dropped_2 text); a │ dropped_2 ───────┼─────────── Hello │ World
能夠看到,雖然b列被drop掉了,可是仍然能夠讀取到數據。less
如何指定列:這裏使用dropped_N來訪問第N列,從1開始計數。函數
侷限:因爲PG刪除了原始列的元數據信息,所以須要在表列名中指定正確的類型,這樣才能進行少許的完整性檢查。包括類型長度、類型對齊、類型修飾符,而且採起的是按值傳遞。post
樣例3:系統列
SELECT * FROM pg_dirtyread('foo') AS t(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean, bar bigint, baz text); tableoid │ ctid │ xmin │ xmax │ cmin │ cmax │ dead │ bar │ baz ──────────┼───────┼──────┼──────┼──────┼──────┼──────┼─────┼─────────────────── 41823 │ (0,1) │ 1484 │ 1485 │ 0 │ 0 │ t │ 1 │ Delete 41823 │ (0,2) │ 1484 │ 0 │ 0 │ 0 │ f │ 2 │ Insert 41823 │ (0,3) │ 1484 │ 1486 │ 0 │ 0 │ t │ 3 │ Update 41823 │ (0,4) │ 1484 │ 1488 │ 0 │ 0 │ f │ 4 │ Not deleted 41823 │ (0,5) │ 1484 │ 1489 │ 1 │ 1 │ f │ 5 │ Not updated 41823 │ (0,6) │ 1486 │ 0 │ 0 │ 0 │ f │ 3 │ Updated 41823 │ (0,7) │ 1489 │ 0 │ 1 │ 1 │ t │ 5 │ Not quite updated 41823 │ (0,8) │ 1490 │ 0 │ 2 │ 2 │ t │ 6 │ Not inserted
能夠看到,xmax和ctid能夠被恢復了。 oid只在11以及更早的版本中才能被恢復。
2、支持的版本
10和11已經支持,2.0之後的版本已經支持12和13,社區仍是很活躍。
3、實現分析
核心代碼有2部分:
一、dirtyread_tupconvert.c 主要實現了dirtyread_convert_tuples_by_name,經過列名進行元組轉換,處理列原信息被清理以及存在表繼承的狀況,關鍵部分是數組:attrMap[],下標從1開始。
重點分析下dirtyread_do_convert_tuple
HeapTuple dirtyread_do_convert_tuple(HeapTuple tuple, TupleConversionMap *map, TransactionId oldest_xmin) { /* * Extract all the values of the old tuple, offsetting the arrays so that * invalues[0] is left NULL and invalues[1] is the first source attribute; * this exactly matches the numbering convention in attrMap. */ heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1); //+1是由於是從下標1開始,從舊的元組中把數據的值獲取到 /* * Transpose into proper fields of the new tuple. 這部分是重點,在這裏完成轉換 */ for (i = 0; i < outnatts; i++) { int j = attrMap; if (j == DeadFakeAttributeNumber) //場景1:明確是dead,直接調用內核的函數HeapTupleIsSurelyDead便可, //定義在tqual.c中,其它場景能夠使用HeapTupleSatisfiesVacuum、HeapTupleSatisfiesMVCC等等,這裏明確是dead,因此使用HeapTupleIsSurelyDead { outvalues = HeapTupleIsSurelyDead(tuple , oldest_xmin); outisnull = false; } else if (j < 0) //場景2:系統列,交給函數heap_getsysattr來處理。 outvalues = heap_getsysattr(tuple, j, map->indesc, &outisnull); else { //場景3:最多見的場景,直接獲取便可。 outvalues = invalues[j]; outisnull = inisnull[j]; } } return heap_form_tuple(map->outdesc, outvalues, outisnull); //從新包裝爲tuple格式 }
二、pg_dirtyread.c 面向客戶的接口在這裏實現。
重點分析下 Datum pg_dirtyread(PG_FUNCTION_ARGS)
第1部分
if (SRF_IS_FIRSTCALL()),這部分比較套路化 { superuser校驗 PG_GETARG_OID獲取表的oid heap_open打開表 get_call_result_type計算結果校驗,不支持複合類型 BlessTupleDesc(tupdesc) 拿到表結構 usr_ctx->map = dirtyread_convert_tuples_by_name(usr_ctx->reltupdesc, funcctx->tuple_desc, "Error converting tuple descriptors!"); //關鍵的一步,這裏使用dirtyread_convert_tuples_by_name函數,。 heap_beginscan(usr_ctx->rel, SnapshotAny...),開始啓動表掃描,這裏使用了SnapshotAny }
第2部分,不斷的獲取每一行,而後對每一行進行轉換,直到掃描結束。
if ((tuplein = heap_getnext(usr_ctx->scan, ForwardScanDirection)) != NULL) { if (usr_ctx->map != NULL) { tuplein = dirtyread_do_convert_tuple(tuplein, usr_ctx->map, usr_ctx->oldest_xmin); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuplein)); } else SRF_RETURN_NEXT(funcctx, heap_copy_tuple_as_datum(tuplein, usr_ctx->reltupdesc)); } else { heap_endscan(usr_ctx->scan); //結束掃描 heap_close(usr_ctx->rel, AccessShareLock); //關閉表 SRF_RETURN_DONE(funcctx); }
總體上實現並非很複雜,理解了這些後,就能夠在此基礎上增長本身的功能了。 而PG的魅力就在於此--架構的開放性,可讓開發者迅速地開發本身的「小程序」出來。