Fix bug in Tid scan.

Commit 147e3722f7 changed Tid scan so that it calls table_beginscan()
and uses the scan option for seq scan. This change caused two issues.

(1) The change caused Tid scan to take a predicate lock on the entire
       relation in serializable transaction even when relation-level
       lock is not necessary. This could lead to an unexpected
       serialization error.

(2) The change caused Tid scan to increment the number of seq_scan
       in pg_stat_*_tables views even though it's not seq scan. This
       could confuse the users.

This commit adds the scan option for Tid scan and makes Tid scan
use it, to avoid those issues.

Back-patch to v12, where the bug was introduced.

Author: Tatsuhito Kasahara
Reviewed-by: Kyotaro Horiguchi, Masahiko Sawada, Fujii Masao
Discussion: https://postgr.es/m/CAP0=ZVKy+gTbFmB6X_UW0pP3WaeJ-fkUWHoD-pExS=at3CY76g@mail.gmail.com
This commit is contained in:
Fujii Masao 2020-02-07 22:00:21 +09:00
parent b059d2f456
commit cb5b28613d
5 changed files with 46 additions and 10 deletions

View File

@ -143,9 +143,8 @@ TidListEval(TidScanState *tidstate)
*/ */
if (tidstate->ss.ss_currentScanDesc == NULL) if (tidstate->ss.ss_currentScanDesc == NULL)
tidstate->ss.ss_currentScanDesc = tidstate->ss.ss_currentScanDesc =
table_beginscan(tidstate->ss.ss_currentRelation, table_beginscan_tid(tidstate->ss.ss_currentRelation,
tidstate->ss.ps.state->es_snapshot, tidstate->ss.ps.state->es_snapshot);
0, NULL);
scan = tidstate->ss.ss_currentScanDesc; scan = tidstate->ss.ss_currentScanDesc;
/* /*

View File

@ -381,7 +381,7 @@ currtid_byreloid(PG_FUNCTION_ARGS)
ItemPointerCopy(tid, result); ItemPointerCopy(tid, result);
snapshot = RegisterSnapshot(GetLatestSnapshot()); snapshot = RegisterSnapshot(GetLatestSnapshot());
scan = table_beginscan(rel, snapshot, 0, NULL); scan = table_beginscan_tid(rel, snapshot);
table_tuple_get_latest_tid(scan, result); table_tuple_get_latest_tid(scan, result);
table_endscan(scan); table_endscan(scan);
UnregisterSnapshot(snapshot); UnregisterSnapshot(snapshot);
@ -419,7 +419,7 @@ currtid_byrelname(PG_FUNCTION_ARGS)
ItemPointerCopy(tid, result); ItemPointerCopy(tid, result);
snapshot = RegisterSnapshot(GetLatestSnapshot()); snapshot = RegisterSnapshot(GetLatestSnapshot());
scan = table_beginscan(rel, snapshot, 0, NULL); scan = table_beginscan_tid(rel, snapshot);
table_tuple_get_latest_tid(scan, result); table_tuple_get_latest_tid(scan, result);
table_endscan(scan); table_endscan(scan);
UnregisterSnapshot(snapshot); UnregisterSnapshot(snapshot);

View File

@ -47,18 +47,19 @@ typedef enum ScanOptions
SO_TYPE_SEQSCAN = 1 << 0, SO_TYPE_SEQSCAN = 1 << 0,
SO_TYPE_BITMAPSCAN = 1 << 1, SO_TYPE_BITMAPSCAN = 1 << 1,
SO_TYPE_SAMPLESCAN = 1 << 2, SO_TYPE_SAMPLESCAN = 1 << 2,
SO_TYPE_ANALYZE = 1 << 3, SO_TYPE_TIDSCAN = 1 << 3,
SO_TYPE_ANALYZE = 1 << 4,
/* several of SO_ALLOW_* may be specified */ /* several of SO_ALLOW_* may be specified */
/* allow or disallow use of access strategy */ /* allow or disallow use of access strategy */
SO_ALLOW_STRAT = 1 << 4, SO_ALLOW_STRAT = 1 << 5,
/* report location to syncscan logic? */ /* report location to syncscan logic? */
SO_ALLOW_SYNC = 1 << 5, SO_ALLOW_SYNC = 1 << 6,
/* verify visibility page-at-a-time? */ /* verify visibility page-at-a-time? */
SO_ALLOW_PAGEMODE = 1 << 6, SO_ALLOW_PAGEMODE = 1 << 7,
/* unregister snapshot at scan end? */ /* unregister snapshot at scan end? */
SO_TEMP_SNAPSHOT = 1 << 7 SO_TEMP_SNAPSHOT = 1 << 8
} ScanOptions; } ScanOptions;
/* /*
@ -829,6 +830,19 @@ table_beginscan_sampling(Relation rel, Snapshot snapshot,
return rel->rd_tableam->scan_begin(rel, snapshot, nkeys, key, NULL, flags); return rel->rd_tableam->scan_begin(rel, snapshot, nkeys, key, NULL, flags);
} }
/*
* table_beginscan_tid is an alternative entry point for setting up a
* TableScanDesc for a Tid scan. As with bitmap scans, it's worth using
* the same data structure although the behavior is rather different.
*/
static inline TableScanDesc
table_beginscan_tid(Relation rel, Snapshot snapshot)
{
uint32 flags = SO_TYPE_TIDSCAN;
return rel->rd_tableam->scan_begin(rel, snapshot, 0, NULL, NULL, flags);
}
/* /*
* table_beginscan_analyze is an alternative entry point for setting up a * table_beginscan_analyze is an alternative entry point for setting up a
* TableScanDesc for an ANALYZE scan. As with bitmap scans, it's worth using * TableScanDesc for an ANALYZE scan. As with bitmap scans, it's worth using

View File

@ -277,4 +277,20 @@ SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
(1 row) (1 row)
RESET enable_hashjoin; RESET enable_hashjoin;
-- check predicate lock on CTID
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM tidscan WHERE ctid = '(0,1)';
id
----
1
(1 row)
-- locktype should be 'tuple'
SELECT locktype, mode FROM pg_locks WHERE pid = pg_backend_pid() AND mode = 'SIReadLock';
locktype | mode
----------+------------
tuple | SIReadLock
(1 row)
ROLLBACK;
DROP TABLE tidscan; DROP TABLE tidscan;

View File

@ -94,4 +94,11 @@ SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid; SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
RESET enable_hashjoin; RESET enable_hashjoin;
-- check predicate lock on CTID
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM tidscan WHERE ctid = '(0,1)';
-- locktype should be 'tuple'
SELECT locktype, mode FROM pg_locks WHERE pid = pg_backend_pid() AND mode = 'SIReadLock';
ROLLBACK;
DROP TABLE tidscan; DROP TABLE tidscan;