Will tables partitioned with inheritance based partitioning always take AccessShareLock locks on all child tables, even when constraints would exclude those partitions?
I'm currently trying to reduce the number of AccessShareLocks taken when querying an inheritance based partitioned table. We've been seeing a large amount of waits on LWLock:LockManager on this table, so we are attempting to reduce the number of AccessShareLocks taken by the most frequent queries on this table. The table uses inheritance based partitioning and CHECK constraints. The parent table items is where rows are inserted, and later archived to finished_items when in an appropriate state.
The table structure looks like:
CREATE TABLE items (
id bigint NOT NULL,
state character varying(255) DEFAULT 'pending'::character varying NOT NULL,
deleted_at boolean,
CONSTRAINT state_valid CHECK (((state)::text = ANY (ARRAY[('accepted'::character varying)::text, ('assigned'::character varying)::text, ('blocked'::character varying)::text, ('broken'::character varying)::text, ('canceled'::character varying)::text, ('canceling'::character varying)::text, ('finished'::character varying)::text, ('limited'::character varying)::text, ('expired'::character varying)::text])))
);
CREATE INDEX ON items(state);
CREATE TABLE finished_items (
id bigint NOT NULL,
state character varying(255) DEFAULT 'pending'::character varying NOT NULL,
deleted_at boolean,
CONSTRAINT state_is_finished CHECK (((state)::text = ANY (ARRAY[('finished'::character varying)::text, ('broken'::character varying)::text, ('expired'::character varying)::text, ('canceled'::character varying)::text, ('timed_out'::character varying)::text])))
) INHERITS (items);
CREATE TABLE deleted_items (
id bigint NOT NULL,
state character varying(255) DEFAULT 'pending'::character varying NOT NULL,
deleted_at boolean,
CONSTRAINT is_deleted CHECK ((deleted_at IS NOT NULL))
) INHERITS (items);
Notably finished_items only contains states: finished, broken, expired and timed_out.
70% of rows are held within finished_items and many queries explicitly filter queries on items to avoid scanning finished_items by using state = assigned. Query plans reflect this and exclude finished_items. However, we've noticed that AccessShareLocks are being taken out on all child tables, even when CHECK constraints on the finished_items and deleted_items would prevent rows matching the filter condition on state existing in the finished_items table, and the filter condition on deleted_at should exclude rows from deleted_items. We currently set constraint_exclusion to partition.
A query on this table would look like:
SELECT * FROM items WHERE state IN ('accepted', 'assigned') AND deleted_at IS NULL;
The query plan looks like:
[items_db] # EXPLAIN SELECT * FROM items WHERE state IN ('accepted', 'assigned') AND deleted_at IS NULL;
QUERY PLAN
--------------------------------------------------------------------------------------------
Seq Scan on items (cost=0.00..0.00 rows=1 width=525)
Filter: ((deleted_at IS NULL) AND ((state)::text = ANY ('{accepted,assigned}'::text[])))
(2 rows)
Time: 4.879 ms
The resulting locks acquired by the query look like:
[items_db] # SELECT relation::regclass, mode FROM pg_locks WHERE pid = pg_backend_pid();
relation | mode
-----------------+-----------------
pg_locks | AccessShareLock
items_state_idx | AccessShareLock
deleted_items | AccessShareLock
finished_items | AccessShareLock
items | AccessShareLock
¤ | ExclusiveLock
(6 rows)
I've discovered that changing the query to use the ONLY keyword would reduce the AccessShareLock locks taken to only the parent table:
SELECT * FROM ONLY items WHERE state IN ('accepted', 'assigned') AND deleted_at IS NULL;
[items_db] # SELECT relation::regclass, mode FROM pg_locks WHERE pid = pg_backend_pid();
relation | mode
-----------------+-----------------
pg_locks | AccessShareLock
items_state_idx | AccessShareLock
items | AccessShareLock
¤ | ExclusiveLock
(4 rows)
However, is there any additional configuration (CHECK constraints, configuration options) on the table we can add to prevent it from scanning all child tables when WHERE conditions would prevent rows from matching the query filters in those tables?