J'ai une base de données Postgres avec une table partitionnée qui contiendra environ 2 000 000 000 entrées.
J'ai défini une base de données partitionnée basée sur la première lettre de "Identifiant" - ceci est divisée en 37 sous-tables, [0-9, A-Z, défaut (Catchall pour tout le reste)].
La base de données est très simple et directe, et est définie ci-dessous.
create table entries (
id bigserial,
identifier text null,
password text null,
additional_fields jsonb
)
partition by list (lower(left(identifier, 1)));
ALTER DATABASE credentials SET constraint_exclusion=on;
CREATE TABLE entries_0 PARTITION OF entries for values in ('0');
CREATE TABLE entries_1 PARTITION OF entries for values in ('1');
CREATE TABLE entries_2 PARTITION OF entries for values in ('2');
CREATE TABLE entries_3 PARTITION OF entries for values in ('3');
...
CREATE TABLE entries_z PARTITION OF entries for values in ('z');
ALTER TABLE entries_0 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '0');
ALTER TABLE entries_1 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '1');
ALTER TABLE entries_2 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '2');
ALTER TABLE entries_3 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '3');
...
ALTER TABLE entries_z ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = 'z');
CREATE INDEX ident_idx on entries(identifier);
Cependant, lorsque j'exécute un EXPLAIN
, il dit que cela fait toujours une analyse séquentielle.
EXPLAIN SELECT * FROM entries where identifier = 'some_identifier_from_subtable_s' LIMIT 1;
Production:
Limit (cost=0.00..140.92 rows=1 width=104)
-> Append (cost=0.00..43418531.72 rows=308113 width=104)
-> Seq Scan on entries_0 (cost=0.00..23239.38 rows=2 width=68)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_1 (cost=0.00..150187.81 rows=6 width=68)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_2 (cost=0.00..94694.38 rows=4 width=67)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_3 (cost=0.00..81656.71 rows=3 width=67)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
... etc.
-> Seq Scan on entries_z (cost=0.00..579207.95 rows=13 width=69)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_default (cost=0.00..15582.36 rows=4 width=69)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
Qu'est-ce que je fais mal? Le partitionnement intelligent devrait être capable de rediriger la requête à la partition entries_s
, ne devrait-elle pas? Et puis la fonction CREATE INDEX ident_idx on entries(identifier);
devrait faire passer la requête dans l'index?
Après avoir ajouté la requête explicite, le nouveau plan ressemble à ceci:
FROM entries
WHERE identifier = 'some_identifier_from_subtable_s'
AND lower(left(identifier, 1)) = 's' -- 1st letter of above identifier
LIMIT 1;
Production:
Limit (cost=1000.00..2583053.76 rows=1 width=71)
-> Gather (cost=1000.00..2583053.76 rows=1 width=71)
Workers Planned: 2
-> Parallel Append (cost=0.00..2582053.66 rows=1 width=71)
-> Parallel Seq Scan on entries_s (cost=0.00..2582053.66 rows=1 width=71)
Filter: ((identifier = 'some_identifier_from_subtable_s'::text) AND (lower("left"(identifier, 1)) = 's'::text))
Cela fait toujours une analyse séquentielle sur entries_s
. Déclarant l'index avec CREATE INDEX ident_idx on entries(identifier);
non propagez-la à toutes les partitions?
Essayez d'ajouter la clé de partition explicitement (redondante!). Comme:
SELECT *
FROM entries
WHERE identifier = 'some_identifier_from_subtable_s'
AND lower(left(identifier, 1)) = 's' -- 1st letter of above identifier
LIMIT 1;
Cela devrait permettre aux postgres de comprendre qu'il peut tailler toutes les autres partitions de la requête.
Peut être dérivé de $1
bien sûr:
AND lower(left(identifier, 1)) = lower(left($1, 1))
Vous verrez toujours une analyse séquentielle sur la partition unique, sauf si vous créez un index comme si vous aviez à l'esprit:
CREATE INDEX ident_idx on entries(identifier);
Cela fonctionne à Postgres 11 ou plus tard car, citant le manuel :
Lorsque
CREATE INDEX
est invoqué sur une table partitionnée, le comportement par défaut consiste à recueillir toutes les partitions pour s'assurer qu'elles disposent d'index correspondants.
Dans Postgres 10 ou plus, vous devez créer des index par partition.
Vous devrez peut-être exécuter ANALYZE
sur la table après avoir créé l'index si vous suivez la requête immédiatement, avant que Autovacuum ait eu le temps de lancer.