mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-10-02 02:51:15 +02:00
d7744d50a5
A NULL result should be reported when a stats timestamp is set to 0, but
c037471
missed that, leading to a confusing timestamp value after for
example a DML on a freshly-created relation with no scans done on it
yet.
This impacted the following attributes for two system views:
- pg_stat_all_tables.last_idx_scan
- pg_stat_all_tables.last_seq_scan
- pg_stat_all_indexes.last_idx_scan
Reported-by: Robert Treat
Analyzed-by: Peter Eisentraut
Author: Dave Page
Discussion: https://postgr.es/m/CABV9wwPzMfSaz3EfKXXDxKmMprbxwF5r6WPuxqA=5mzRUqfTGg@mail.gmail.com
1130 lines
30 KiB
Plaintext
1130 lines
30 KiB
Plaintext
--
|
|
-- Test cumulative stats system
|
|
--
|
|
-- Must be run after tenk2 has been created (by create_table),
|
|
-- populated (by create_misc) and indexed (by create_index).
|
|
--
|
|
-- conditio sine qua non
|
|
SHOW track_counts; -- must be on
|
|
track_counts
|
|
--------------
|
|
on
|
|
(1 row)
|
|
|
|
-- ensure that both seqscan and indexscan plans are allowed
|
|
SET enable_seqscan TO on;
|
|
SET enable_indexscan TO on;
|
|
-- for the moment, we don't want index-only scans here
|
|
SET enable_indexonlyscan TO off;
|
|
-- not enabled by default, but we want to test it...
|
|
SET track_functions TO 'all';
|
|
-- record dboid for later use
|
|
SELECT oid AS dboid from pg_database where datname = current_database() \gset
|
|
-- save counters
|
|
BEGIN;
|
|
SET LOCAL stats_fetch_consistency = snapshot;
|
|
CREATE TABLE prevstats AS
|
|
SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
|
|
(b.heap_blks_read + b.heap_blks_hit) AS heap_blks,
|
|
(b.idx_blks_read + b.idx_blks_hit) AS idx_blks,
|
|
pg_stat_get_snapshot_timestamp() as snap_ts
|
|
FROM pg_catalog.pg_stat_user_tables AS t,
|
|
pg_catalog.pg_statio_user_tables AS b
|
|
WHERE t.relname='tenk2' AND b.relname='tenk2';
|
|
COMMIT;
|
|
-- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
|
|
CREATE TABLE trunc_stats_test(id serial);
|
|
CREATE TABLE trunc_stats_test1(id serial, stuff text);
|
|
CREATE TABLE trunc_stats_test2(id serial);
|
|
CREATE TABLE trunc_stats_test3(id serial, stuff text);
|
|
CREATE TABLE trunc_stats_test4(id serial);
|
|
-- check that n_live_tup is reset to 0 after truncate
|
|
INSERT INTO trunc_stats_test DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test DEFAULT VALUES;
|
|
TRUNCATE trunc_stats_test;
|
|
-- test involving a truncate in a transaction; 4 ins but only 1 live
|
|
INSERT INTO trunc_stats_test1 DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test1 DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test1 DEFAULT VALUES;
|
|
UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
|
|
DELETE FROM trunc_stats_test1 WHERE id = 3;
|
|
BEGIN;
|
|
UPDATE trunc_stats_test1 SET id = id + 100;
|
|
TRUNCATE trunc_stats_test1;
|
|
INSERT INTO trunc_stats_test1 DEFAULT VALUES;
|
|
COMMIT;
|
|
-- use a savepoint: 1 insert, 1 live
|
|
BEGIN;
|
|
INSERT INTO trunc_stats_test2 DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test2 DEFAULT VALUES;
|
|
SAVEPOINT p1;
|
|
INSERT INTO trunc_stats_test2 DEFAULT VALUES;
|
|
TRUNCATE trunc_stats_test2;
|
|
INSERT INTO trunc_stats_test2 DEFAULT VALUES;
|
|
RELEASE SAVEPOINT p1;
|
|
COMMIT;
|
|
-- rollback a savepoint: this should count 4 inserts and have 2
|
|
-- live tuples after commit (and 2 dead ones due to aborted subxact)
|
|
BEGIN;
|
|
INSERT INTO trunc_stats_test3 DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test3 DEFAULT VALUES;
|
|
SAVEPOINT p1;
|
|
INSERT INTO trunc_stats_test3 DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test3 DEFAULT VALUES;
|
|
TRUNCATE trunc_stats_test3;
|
|
INSERT INTO trunc_stats_test3 DEFAULT VALUES;
|
|
ROLLBACK TO SAVEPOINT p1;
|
|
COMMIT;
|
|
-- rollback a truncate: this should count 2 inserts and produce 2 dead tuples
|
|
BEGIN;
|
|
INSERT INTO trunc_stats_test4 DEFAULT VALUES;
|
|
INSERT INTO trunc_stats_test4 DEFAULT VALUES;
|
|
TRUNCATE trunc_stats_test4;
|
|
INSERT INTO trunc_stats_test4 DEFAULT VALUES;
|
|
ROLLBACK;
|
|
-- do a seqscan
|
|
SELECT count(*) FROM tenk2;
|
|
count
|
|
-------
|
|
10000
|
|
(1 row)
|
|
|
|
-- do an indexscan
|
|
-- make sure it is not a bitmap scan, which might skip fetching heap tuples
|
|
SET enable_bitmapscan TO off;
|
|
SELECT count(*) FROM tenk2 WHERE unique1 = 1;
|
|
count
|
|
-------
|
|
1
|
|
(1 row)
|
|
|
|
RESET enable_bitmapscan;
|
|
-- ensure pending stats are flushed
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
-- check effects
|
|
BEGIN;
|
|
SET LOCAL stats_fetch_consistency = snapshot;
|
|
SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup
|
|
FROM pg_stat_user_tables
|
|
WHERE relname like 'trunc_stats_test%' order by relname;
|
|
relname | n_tup_ins | n_tup_upd | n_tup_del | n_live_tup | n_dead_tup
|
|
-------------------+-----------+-----------+-----------+------------+------------
|
|
trunc_stats_test | 3 | 0 | 0 | 0 | 0
|
|
trunc_stats_test1 | 4 | 2 | 1 | 1 | 0
|
|
trunc_stats_test2 | 1 | 0 | 0 | 1 | 0
|
|
trunc_stats_test3 | 4 | 0 | 0 | 2 | 2
|
|
trunc_stats_test4 | 2 | 0 | 0 | 0 | 2
|
|
(5 rows)
|
|
|
|
SELECT st.seq_scan >= pr.seq_scan + 1,
|
|
st.seq_tup_read >= pr.seq_tup_read + cl.reltuples,
|
|
st.idx_scan >= pr.idx_scan + 1,
|
|
st.idx_tup_fetch >= pr.idx_tup_fetch + 1
|
|
FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
|
|
WHERE st.relname='tenk2' AND cl.relname='tenk2';
|
|
?column? | ?column? | ?column? | ?column?
|
|
----------+----------+----------+----------
|
|
t | t | t | t
|
|
(1 row)
|
|
|
|
SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages,
|
|
st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1
|
|
FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr
|
|
WHERE st.relname='tenk2' AND cl.relname='tenk2';
|
|
?column? | ?column?
|
|
----------+----------
|
|
t | t
|
|
(1 row)
|
|
|
|
SELECT pr.snap_ts < pg_stat_get_snapshot_timestamp() as snapshot_newer
|
|
FROM prevstats AS pr;
|
|
snapshot_newer
|
|
----------------
|
|
t
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
----
|
|
-- Basic tests for track_functions
|
|
---
|
|
CREATE FUNCTION stats_test_func1() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
|
|
SELECT 'stats_test_func1()'::regprocedure::oid AS stats_test_func1_oid \gset
|
|
CREATE FUNCTION stats_test_func2() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
|
|
SELECT 'stats_test_func2()'::regprocedure::oid AS stats_test_func2_oid \gset
|
|
-- test that stats are accumulated
|
|
BEGIN;
|
|
SET LOCAL stats_fetch_consistency = none;
|
|
SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_function_calls
|
|
----------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_xact_function_calls
|
|
---------------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_test_func1();
|
|
stats_test_func1
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_xact_function_calls
|
|
---------------------------------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT stats_test_func1();
|
|
stats_test_func1
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_xact_function_calls
|
|
---------------------------------
|
|
2
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_function_calls
|
|
----------------------------
|
|
0
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
-- Verify that function stats are not transactional
|
|
-- rolled back savepoint in committing transaction
|
|
BEGIN;
|
|
SELECT stats_test_func2();
|
|
stats_test_func2
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
SAVEPOINT foo;
|
|
SELECT stats_test_func2();
|
|
stats_test_func2
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
ROLLBACK TO SAVEPOINT foo;
|
|
SELECT pg_stat_get_xact_function_calls(:stats_test_func2_oid);
|
|
pg_stat_get_xact_function_calls
|
|
---------------------------------
|
|
2
|
|
(1 row)
|
|
|
|
SELECT stats_test_func2();
|
|
stats_test_func2
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
-- rolled back transaction
|
|
BEGIN;
|
|
SELECT stats_test_func2();
|
|
stats_test_func2
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
ROLLBACK;
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
-- check collected stats
|
|
SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
|
|
funcname | calls
|
|
------------------+-------
|
|
stats_test_func1 | 2
|
|
(1 row)
|
|
|
|
SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid;
|
|
funcname | calls
|
|
------------------+-------
|
|
stats_test_func2 | 4
|
|
(1 row)
|
|
|
|
-- check that a rolled back drop function stats leaves stats alive
|
|
BEGIN;
|
|
SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
|
|
funcname | calls
|
|
------------------+-------
|
|
stats_test_func1 | 2
|
|
(1 row)
|
|
|
|
DROP FUNCTION stats_test_func1();
|
|
-- shouldn't be visible via view
|
|
SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
|
|
funcname | calls
|
|
----------+-------
|
|
(0 rows)
|
|
|
|
-- but still via oid access
|
|
SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_function_calls
|
|
----------------------------
|
|
2
|
|
(1 row)
|
|
|
|
ROLLBACK;
|
|
SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
|
|
funcname | calls
|
|
------------------+-------
|
|
stats_test_func1 | 2
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_function_calls
|
|
----------------------------
|
|
2
|
|
(1 row)
|
|
|
|
-- check that function dropped in main transaction leaves no stats behind
|
|
BEGIN;
|
|
DROP FUNCTION stats_test_func1();
|
|
COMMIT;
|
|
SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
|
|
funcname | calls
|
|
----------+-------
|
|
(0 rows)
|
|
|
|
SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
|
|
pg_stat_get_function_calls
|
|
----------------------------
|
|
|
|
(1 row)
|
|
|
|
-- check that function dropped in a subtransaction leaves no stats behind
|
|
BEGIN;
|
|
SELECT stats_test_func2();
|
|
stats_test_func2
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
SAVEPOINT a;
|
|
SELECT stats_test_func2();
|
|
stats_test_func2
|
|
------------------
|
|
|
|
(1 row)
|
|
|
|
SAVEPOINT b;
|
|
DROP FUNCTION stats_test_func2();
|
|
COMMIT;
|
|
SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid;
|
|
funcname | calls
|
|
----------+-------
|
|
(0 rows)
|
|
|
|
SELECT pg_stat_get_function_calls(:stats_test_func2_oid);
|
|
pg_stat_get_function_calls
|
|
----------------------------
|
|
|
|
(1 row)
|
|
|
|
-- Check that stats for relations are dropped. For that we need to access stats
|
|
-- by oid after the DROP TABLE. Save oids.
|
|
CREATE TABLE drop_stats_test();
|
|
INSERT INTO drop_stats_test DEFAULT VALUES;
|
|
SELECT 'drop_stats_test'::regclass::oid AS drop_stats_test_oid \gset
|
|
CREATE TABLE drop_stats_test_xact();
|
|
INSERT INTO drop_stats_test_xact DEFAULT VALUES;
|
|
SELECT 'drop_stats_test_xact'::regclass::oid AS drop_stats_test_xact_oid \gset
|
|
CREATE TABLE drop_stats_test_subxact();
|
|
INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
|
|
SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \gset
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
1
|
|
(1 row)
|
|
|
|
DROP TABLE drop_stats_test;
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
0
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
0
|
|
(1 row)
|
|
|
|
-- check that rollback protects against having stats dropped and that local
|
|
-- modifications don't pose a problem
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_tuples_inserted
|
|
-----------------------------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
0
|
|
(1 row)
|
|
|
|
BEGIN;
|
|
INSERT INTO drop_stats_test_xact DEFAULT VALUES;
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
1
|
|
(1 row)
|
|
|
|
DROP TABLE drop_stats_test_xact;
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
0
|
|
(1 row)
|
|
|
|
ROLLBACK;
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_tuples_inserted
|
|
-----------------------------
|
|
2
|
|
(1 row)
|
|
|
|
-- transactional drop
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_tuples_inserted
|
|
-----------------------------
|
|
2
|
|
(1 row)
|
|
|
|
BEGIN;
|
|
INSERT INTO drop_stats_test_xact DEFAULT VALUES;
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
1
|
|
(1 row)
|
|
|
|
DROP TABLE drop_stats_test_xact;
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
0
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
0
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
|
|
pg_stat_get_tuples_inserted
|
|
-----------------------------
|
|
0
|
|
(1 row)
|
|
|
|
-- savepoint rollback (2 levels)
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
1
|
|
(1 row)
|
|
|
|
BEGIN;
|
|
INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
|
|
SAVEPOINT sp1;
|
|
INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
2
|
|
(1 row)
|
|
|
|
SAVEPOINT sp2;
|
|
DROP TABLE drop_stats_test_subxact;
|
|
ROLLBACK TO SAVEPOINT sp2;
|
|
SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_xact_tuples_inserted
|
|
----------------------------------
|
|
2
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
3
|
|
(1 row)
|
|
|
|
-- savepoint rolback (1 level)
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
3
|
|
(1 row)
|
|
|
|
BEGIN;
|
|
SAVEPOINT sp1;
|
|
DROP TABLE drop_stats_test_subxact;
|
|
SAVEPOINT sp2;
|
|
ROLLBACK TO SAVEPOINT sp1;
|
|
COMMIT;
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
3
|
|
(1 row)
|
|
|
|
-- and now actually drop
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
3
|
|
(1 row)
|
|
|
|
BEGIN;
|
|
SAVEPOINT sp1;
|
|
DROP TABLE drop_stats_test_subxact;
|
|
SAVEPOINT sp2;
|
|
RELEASE SAVEPOINT sp1;
|
|
COMMIT;
|
|
SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
|
|
pg_stat_get_live_tuples
|
|
-------------------------
|
|
0
|
|
(1 row)
|
|
|
|
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
|
|
DROP TABLE prevstats;
|
|
-----
|
|
-- Test that last_seq_scan, last_idx_scan are correctly maintained
|
|
--
|
|
-- Perform test using a temporary table. That way autovacuum etc won't
|
|
-- interfere. To be able to check that timestamps increase, we sleep for 100ms
|
|
-- between tests, assuming that there aren't systems with a coarser timestamp
|
|
-- granularity.
|
|
-----
|
|
BEGIN;
|
|
CREATE TEMPORARY TABLE test_last_scan(idx_col int primary key, noidx_col int);
|
|
INSERT INTO test_last_scan(idx_col, noidx_col) VALUES(1, 1);
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT last_seq_scan, last_idx_scan FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass;
|
|
last_seq_scan | last_idx_scan
|
|
---------------+---------------
|
|
|
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
SELECT pg_stat_reset_single_table_counters('test_last_scan'::regclass);
|
|
pg_stat_reset_single_table_counters
|
|
-------------------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT seq_scan, idx_scan FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass;
|
|
seq_scan | idx_scan
|
|
----------+----------
|
|
0 | 0
|
|
(1 row)
|
|
|
|
-- ensure we start out with exactly one index and sequential scan
|
|
BEGIN;
|
|
SET LOCAL enable_seqscan TO on;
|
|
SET LOCAL enable_indexscan TO on;
|
|
SET LOCAL enable_bitmapscan TO off;
|
|
EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE noidx_col = 1;
|
|
QUERY PLAN
|
|
----------------------------------
|
|
Aggregate
|
|
-> Seq Scan on test_last_scan
|
|
Filter: (noidx_col = 1)
|
|
(3 rows)
|
|
|
|
SELECT count(*) FROM test_last_scan WHERE noidx_col = 1;
|
|
count
|
|
-------
|
|
1
|
|
(1 row)
|
|
|
|
SET LOCAL enable_seqscan TO off;
|
|
EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE idx_col = 1;
|
|
QUERY PLAN
|
|
--------------------------------------------------------------
|
|
Aggregate
|
|
-> Index Scan using test_last_scan_pkey on test_last_scan
|
|
Index Cond: (idx_col = 1)
|
|
(3 rows)
|
|
|
|
SELECT count(*) FROM test_last_scan WHERE idx_col = 1;
|
|
count
|
|
-------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
-- fetch timestamps from before the next test
|
|
SELECT last_seq_scan AS test_last_seq, last_idx_scan AS test_last_idx
|
|
FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass \gset
|
|
SELECT pg_sleep(0.1); -- assume a minimum timestamp granularity of 100ms
|
|
pg_sleep
|
|
----------
|
|
|
|
(1 row)
|
|
|
|
-- cause one sequential scan
|
|
BEGIN;
|
|
SET LOCAL enable_seqscan TO on;
|
|
SET LOCAL enable_indexscan TO off;
|
|
SET LOCAL enable_bitmapscan TO off;
|
|
EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE noidx_col = 1;
|
|
QUERY PLAN
|
|
----------------------------------
|
|
Aggregate
|
|
-> Seq Scan on test_last_scan
|
|
Filter: (noidx_col = 1)
|
|
(3 rows)
|
|
|
|
SELECT count(*) FROM test_last_scan WHERE noidx_col = 1;
|
|
count
|
|
-------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
-- check that just sequential scan stats were incremented
|
|
SELECT seq_scan, :'test_last_seq' < last_seq_scan AS seq_ok, idx_scan, :'test_last_idx' = last_idx_scan AS idx_ok
|
|
FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass;
|
|
seq_scan | seq_ok | idx_scan | idx_ok
|
|
----------+--------+----------+--------
|
|
2 | t | 1 | t
|
|
(1 row)
|
|
|
|
-- fetch timestamps from before the next test
|
|
SELECT last_seq_scan AS test_last_seq, last_idx_scan AS test_last_idx
|
|
FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass \gset
|
|
SELECT pg_sleep(0.1);
|
|
pg_sleep
|
|
----------
|
|
|
|
(1 row)
|
|
|
|
-- cause one index scan
|
|
BEGIN;
|
|
SET LOCAL enable_seqscan TO off;
|
|
SET LOCAL enable_indexscan TO on;
|
|
SET LOCAL enable_bitmapscan TO off;
|
|
EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE idx_col = 1;
|
|
QUERY PLAN
|
|
--------------------------------------------------------------
|
|
Aggregate
|
|
-> Index Scan using test_last_scan_pkey on test_last_scan
|
|
Index Cond: (idx_col = 1)
|
|
(3 rows)
|
|
|
|
SELECT count(*) FROM test_last_scan WHERE idx_col = 1;
|
|
count
|
|
-------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
-- check that just index scan stats were incremented
|
|
SELECT seq_scan, :'test_last_seq' = last_seq_scan AS seq_ok, idx_scan, :'test_last_idx' < last_idx_scan AS idx_ok
|
|
FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass;
|
|
seq_scan | seq_ok | idx_scan | idx_ok
|
|
----------+--------+----------+--------
|
|
2 | t | 2 | t
|
|
(1 row)
|
|
|
|
-- fetch timestamps from before the next test
|
|
SELECT last_seq_scan AS test_last_seq, last_idx_scan AS test_last_idx
|
|
FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass \gset
|
|
SELECT pg_sleep(0.1);
|
|
pg_sleep
|
|
----------
|
|
|
|
(1 row)
|
|
|
|
-- cause one bitmap index scan
|
|
BEGIN;
|
|
SET LOCAL enable_seqscan TO off;
|
|
SET LOCAL enable_indexscan TO off;
|
|
SET LOCAL enable_bitmapscan TO on;
|
|
EXPLAIN (COSTS off) SELECT count(*) FROM test_last_scan WHERE idx_col = 1;
|
|
QUERY PLAN
|
|
------------------------------------------------------
|
|
Aggregate
|
|
-> Bitmap Heap Scan on test_last_scan
|
|
Recheck Cond: (idx_col = 1)
|
|
-> Bitmap Index Scan on test_last_scan_pkey
|
|
Index Cond: (idx_col = 1)
|
|
(5 rows)
|
|
|
|
SELECT count(*) FROM test_last_scan WHERE idx_col = 1;
|
|
count
|
|
-------
|
|
1
|
|
(1 row)
|
|
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
-- check that just index scan stats were incremented
|
|
SELECT seq_scan, :'test_last_seq' = last_seq_scan AS seq_ok, idx_scan, :'test_last_idx' < last_idx_scan AS idx_ok
|
|
FROM pg_stat_all_tables WHERE relid = 'test_last_scan'::regclass;
|
|
seq_scan | seq_ok | idx_scan | idx_ok
|
|
----------+--------+----------+--------
|
|
2 | t | 3 | t
|
|
(1 row)
|
|
|
|
-----
|
|
-- Test that various stats views are being properly populated
|
|
-----
|
|
-- Test that sessions is incremented when a new session is started in pg_stat_database
|
|
SELECT sessions AS db_stat_sessions FROM pg_stat_database WHERE datname = (SELECT current_database()) \gset
|
|
\c
|
|
SELECT pg_stat_force_next_flush();
|
|
pg_stat_force_next_flush
|
|
--------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT sessions > :db_stat_sessions FROM pg_stat_database WHERE datname = (SELECT current_database());
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
-- Test pg_stat_bgwriter checkpointer-related stats, together with pg_stat_wal
|
|
SELECT checkpoints_req AS rqst_ckpts_before FROM pg_stat_bgwriter \gset
|
|
-- Test pg_stat_wal (and make a temp table so our temp schema exists)
|
|
SELECT wal_bytes AS wal_bytes_before FROM pg_stat_wal \gset
|
|
CREATE TEMP TABLE test_stats_temp AS SELECT 17;
|
|
DROP TABLE test_stats_temp;
|
|
-- Checkpoint twice: The checkpointer reports stats after reporting completion
|
|
-- of the checkpoint. But after a second checkpoint we'll see at least the
|
|
-- results of the first.
|
|
CHECKPOINT;
|
|
CHECKPOINT;
|
|
SELECT checkpoints_req > :rqst_ckpts_before FROM pg_stat_bgwriter;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT wal_bytes > :wal_bytes_before FROM pg_stat_wal;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
-- Test pg_stat_get_backend_idset() and some allied functions.
|
|
-- In particular, verify that their notion of backend ID matches
|
|
-- our temp schema index.
|
|
SELECT (current_schemas(true))[1] = ('pg_temp_' || beid::text) AS match
|
|
FROM pg_stat_get_backend_idset() beid
|
|
WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid();
|
|
match
|
|
-------
|
|
t
|
|
(1 row)
|
|
|
|
-----
|
|
-- Test that resetting stats works for reset timestamp
|
|
-----
|
|
-- Test that reset_slru with a specified SLRU works.
|
|
SELECT stats_reset AS slru_commit_ts_reset_ts FROM pg_stat_slru WHERE name = 'CommitTs' \gset
|
|
SELECT stats_reset AS slru_notify_reset_ts FROM pg_stat_slru WHERE name = 'Notify' \gset
|
|
SELECT pg_stat_reset_slru('CommitTs');
|
|
pg_stat_reset_slru
|
|
--------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset > :'slru_commit_ts_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'CommitTs';
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT stats_reset AS slru_commit_ts_reset_ts FROM pg_stat_slru WHERE name = 'CommitTs' \gset
|
|
-- Test that multiple SLRUs are reset when no specific SLRU provided to reset function
|
|
SELECT pg_stat_reset_slru(NULL);
|
|
pg_stat_reset_slru
|
|
--------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset > :'slru_commit_ts_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'CommitTs';
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT stats_reset > :'slru_notify_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'Notify';
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
-- Test that reset_shared with archiver specified as the stats type works
|
|
SELECT stats_reset AS archiver_reset_ts FROM pg_stat_archiver \gset
|
|
SELECT pg_stat_reset_shared('archiver');
|
|
pg_stat_reset_shared
|
|
----------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset > :'archiver_reset_ts'::timestamptz FROM pg_stat_archiver;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT stats_reset AS archiver_reset_ts FROM pg_stat_archiver \gset
|
|
-- Test that reset_shared with bgwriter specified as the stats type works
|
|
SELECT stats_reset AS bgwriter_reset_ts FROM pg_stat_bgwriter \gset
|
|
SELECT pg_stat_reset_shared('bgwriter');
|
|
pg_stat_reset_shared
|
|
----------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset > :'bgwriter_reset_ts'::timestamptz FROM pg_stat_bgwriter;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT stats_reset AS bgwriter_reset_ts FROM pg_stat_bgwriter \gset
|
|
-- Test that reset_shared with wal specified as the stats type works
|
|
SELECT stats_reset AS wal_reset_ts FROM pg_stat_wal \gset
|
|
SELECT pg_stat_reset_shared('wal');
|
|
pg_stat_reset_shared
|
|
----------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset > :'wal_reset_ts'::timestamptz FROM pg_stat_wal;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT stats_reset AS wal_reset_ts FROM pg_stat_wal \gset
|
|
-- Test that reset_shared with no specified stats type doesn't reset anything
|
|
SELECT pg_stat_reset_shared(NULL);
|
|
pg_stat_reset_shared
|
|
----------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset = :'archiver_reset_ts'::timestamptz FROM pg_stat_archiver;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT stats_reset = :'bgwriter_reset_ts'::timestamptz FROM pg_stat_bgwriter;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
SELECT stats_reset = :'wal_reset_ts'::timestamptz FROM pg_stat_wal;
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
-- Test that reset works for pg_stat_database
|
|
-- Since pg_stat_database stats_reset starts out as NULL, reset it once first so we have something to compare it to
|
|
SELECT pg_stat_reset();
|
|
pg_stat_reset
|
|
---------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset AS db_reset_ts FROM pg_stat_database WHERE datname = (SELECT current_database()) \gset
|
|
SELECT pg_stat_reset();
|
|
pg_stat_reset
|
|
---------------
|
|
|
|
(1 row)
|
|
|
|
SELECT stats_reset > :'db_reset_ts'::timestamptz FROM pg_stat_database WHERE datname = (SELECT current_database());
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
----
|
|
-- pg_stat_get_snapshot_timestamp behavior
|
|
----
|
|
BEGIN;
|
|
SET LOCAL stats_fetch_consistency = snapshot;
|
|
-- no snapshot yet, return NULL
|
|
SELECT pg_stat_get_snapshot_timestamp();
|
|
pg_stat_get_snapshot_timestamp
|
|
--------------------------------
|
|
|
|
(1 row)
|
|
|
|
-- any attempt at accessing stats will build snapshot
|
|
SELECT pg_stat_get_function_calls(0);
|
|
pg_stat_get_function_calls
|
|
----------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_snapshot_timestamp() >= NOW();
|
|
?column?
|
|
----------
|
|
t
|
|
(1 row)
|
|
|
|
-- shows NULL again after clearing
|
|
SELECT pg_stat_clear_snapshot();
|
|
pg_stat_clear_snapshot
|
|
------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_snapshot_timestamp();
|
|
pg_stat_get_snapshot_timestamp
|
|
--------------------------------
|
|
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
----
|
|
-- pg_stat_have_stats behavior
|
|
----
|
|
-- fixed-numbered stats exist
|
|
SELECT pg_stat_have_stats('bgwriter', 0, 0);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
-- unknown stats kinds error out
|
|
SELECT pg_stat_have_stats('zaphod', 0, 0);
|
|
ERROR: invalid statistics kind: "zaphod"
|
|
-- db stats have objoid 0
|
|
SELECT pg_stat_have_stats('database', :dboid, 1);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
f
|
|
(1 row)
|
|
|
|
SELECT pg_stat_have_stats('database', :dboid, 0);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
-- pg_stat_have_stats returns true for committed index creation
|
|
CREATE table stats_test_tab1 as select generate_series(1,10) a;
|
|
CREATE index stats_test_idx1 on stats_test_tab1(a);
|
|
SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
|
|
SET enable_seqscan TO off;
|
|
select a from stats_test_tab1 where a = 3;
|
|
a
|
|
---
|
|
3
|
|
(1 row)
|
|
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
-- pg_stat_have_stats returns false for dropped index with stats
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
DROP index stats_test_idx1;
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
f
|
|
(1 row)
|
|
|
|
-- pg_stat_have_stats returns false for rolled back index creation
|
|
BEGIN;
|
|
CREATE index stats_test_idx1 on stats_test_tab1(a);
|
|
SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
|
|
select a from stats_test_tab1 where a = 3;
|
|
a
|
|
---
|
|
3
|
|
(1 row)
|
|
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
ROLLBACK;
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
f
|
|
(1 row)
|
|
|
|
-- pg_stat_have_stats returns true for reindex CONCURRENTLY
|
|
CREATE index stats_test_idx1 on stats_test_tab1(a);
|
|
SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
|
|
select a from stats_test_tab1 where a = 3;
|
|
a
|
|
---
|
|
3
|
|
(1 row)
|
|
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
REINDEX index CONCURRENTLY stats_test_idx1;
|
|
-- false for previous oid
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
f
|
|
(1 row)
|
|
|
|
-- true for new oid
|
|
SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
-- pg_stat_have_stats returns true for a rolled back drop index with stats
|
|
BEGIN;
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
DROP index stats_test_idx1;
|
|
ROLLBACK;
|
|
SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
|
|
pg_stat_have_stats
|
|
--------------------
|
|
t
|
|
(1 row)
|
|
|
|
-- put enable_seqscan back to on
|
|
SET enable_seqscan TO on;
|
|
-- ensure that stats accessors handle NULL input correctly
|
|
SELECT pg_stat_get_replication_slot(NULL);
|
|
pg_stat_get_replication_slot
|
|
------------------------------
|
|
|
|
(1 row)
|
|
|
|
SELECT pg_stat_get_subscription_stats(NULL);
|
|
pg_stat_get_subscription_stats
|
|
--------------------------------
|
|
|
|
(1 row)
|
|
|
|
-- End of Stats Test
|