web-dev-qa-db-fra.com

problèmes de séquence postgres, pids créés manuellement et réinitialisation de séquence appropriée

Les développeurs ont rencontré des problèmes avec les séquences dans notre base de données postgres 9.6 au cours de la dernière année, où ils ont cessé de fonctionner, ils ont donc recours à des choses comme l'insertion de lignes avec un pid créé manuellement en fonction du pid du dernier enregistrement inséré, et la réinitialisation de la séquence à l'aide de ce code:

SELECT pg_catalog.setval(pg_get_serial_sequence('mytable', 'pid'), 
       (SELECT MAX(pid)+1 FROM mytable) );

J'ai suffisamment d'expérience pour savoir que nous devrions pouvoir compter sur la base de données pour créer nos identifiants et que la création de ses propres PID en fonction du dernier maximum n'est pas une "meilleure pratique" ni sûre pour tous les scénarios, même si cela a généralement fonctionné pour notre utilisation quotidienne.

La raison pour laquelle ils ont cessé de fonctionner la première fois est la cause bien connue qu'une table a été restaurée par exemple. Cependant, à ce stade, je pense que le code manuel et les séquences se chevauchent, et le code de réinitialisation de séquence n'est pas solide. Il semble évident que l'insertion du dernier max va saper la séquence, qui a simplement son propre numéro qu'elle incrémente.

Pendant que je lis, je voudrais savoir si quelqu'un me pointe dans la bonne direction aujourd'hui pour récupérer la séquence et travailler sans problème sur une table existante - et la faire fonctionner même si du code est en place qui insérera parfois un pid du code, basé sur le dernier max. (À plus long terme, bien sûr, je suis pleinement conscient qu'il est préférable que tout ce code soit supprimé - mais existe-t-il un moyen de contourner ce code pour l'instant?)

Dans une solution, il y aurait un moyen dans postgreSQL 9.6 de le réinitialiser la séquence elle-même s'il y a un conflit - pas sûr si possible, et je me prépare pour des conférences des plus expérimentés - mais c'est pourquoi je suis ici!

Enfin - et c'est un fait troublant qui m'a amené ici - après avoir réinitialisé la séquence dans pg admin, je vois deux pids dans le tableau dans pg admin 3 et 4, qui apparaît également dans le script de création. La "deuxième" colonne PID ne s'affiche pas lors de l'exécution de\d en psql, ce qui est bien - mais j'ai pensé que cela pourrait être pertinent.

MISE À JOUR - sur ce dernier point, les PID fantômes en double pour une table dans pg admin ont été causés parce qu'il y avait deux séquences pour ce même combo colonne/table, la seconde créée à un moment donné pour essayer de réparer les séquences cassées au fil du temps (par exemple mytable_pid_seq et mytable_pid_seq1). Je ne sais pas pourquoi/comment cela a pu se produire dans la base de données.

6
csdev

Dans une solution, il y aurait un moyen dans postgreSQL 9.6 de le réinitialiser la séquence elle-même s'il y a un conflit - pas sûr si possible, et je me prépare pour des conférences des plus expérimentés - mais c'est pourquoi je suis ici!

Tout d'abord, le vrai correctif serait que le code utilise toujours la séquence au lieu du mélange incohérent de select 1+max(pk) et nextval('seqname').

Cela étant dit, en tant que solution de pansement avant un vrai correctif, vous pouvez avoir un déclencheur sur INSERT pour chaque ligne qui appelle toujours nextval sur la séquence, de sorte que vous êtes sûr que la séquence ne traîne jamais derrière , même lorsque INSERT lui-même manque un appel nextval.

Si la colonne a une DEFAULT nextval('seqname') (définie manuellement ou via une déclaration SERIAL), et un déclencheur appelle nextval en plus du nextval du DEFAULT, cela fera simplement avancer la séquence de deux au lieu d'un. Cela ne devrait pas être un problème pour votre application, car les séquences peuvent de toute façon avoir des trous, en raison de la restauration ou de la mise en cache.

L'appel de setval avec SELECT 1+max(pk) from table fonctionne également, mais uniquement lorsqu'il n'y a pas d'autres transactions utilisant la séquence simultanément. Donc, faire cela dans un déclencheur ne semble pas être une bonne idée. En règle générale, ce type d'ajustement n'est effectué qu'après un chargement en masse (en fait, c'est ce que pg_dump produit lorsque la séquence appartient à la table).

1
Daniel Vérité

La raison pour laquelle ils ont cessé de fonctionner la première fois est la cause bien connue qu'une table a été restaurée par exemple. Cependant, à ce stade, je pense que le code manuel et les séquences se chevauchent, et le code de réinitialisation de séquence n'est pas solide. Il semble évident que l'insertion du dernier max va saper la séquence, qui a simplement son propre numéro qu'elle incrémente.

Si vous effectuez une restauration à partir de pg_dump, Cela ne peut en aucun cas être le problème. Prenez par exemple,

CREATE TABLE foo (id serial PRIMARY KEY);
INSERT INTO foo DEFAULT VALUES;

Cela génère une table comme celle-ci,

                         Table "public.foo"
 Column |  Type   |                    Modifiers                     
--------+---------+--------------------------------------------------
 id     | integer | not null default nextval('foo_id_seq'::regclass)
Indexes:
    "foo_pkey" PRIMARY KEY, btree (id)

Vous pouvez voir ici que le type d'id est juste int et que toute la séquence est par défaut à nextval. En utilisant pg_dump, Cela générera un vidage comme celui-ci (suppression des commentaires et des autorisations)

CREATE TABLE foo (
    id integer NOT NULL
);

CREATE SEQUENCE foo_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

ALTER TABLE ONLY foo ALTER COLUMN id SET DEFAULT nextval('foo_id_seq'::regclass);

COPY foo (id) FROM stdin;
1
\.

SELECT pg_catalog.setval('foo_id_seq', 1, true);

ALTER TABLE ONLY foo
    ADD CONSTRAINT foo_pkey PRIMARY KEY (id);

C'est beaucoup de choses, mais c'est le moyen le plus sûr de restaurer même une simple table. Alors remarquez ici l'appel à pg_catalog.setval

SELECT pg_catalog.setval('foo_id_seq', 1, true);

Après avoir fusionné dans d'autres pkids potentiellement plus élevés, vous devrez l'appeler avec la nouvelle valeur la plus élevée.

SELECT pg_catalog.setval(
  'foo_id_seq',
  (SELECT max(id) FROM foo),
  true
);

Puis alto, vous êtes de retour au travail. Ils ne sont pas du tout cassés.

Remarque importante, ceci concerne les types serial. La page 10 changera cette interface si vous utilisez Colonnes d'identité et ce sera à la place une commande ALTER TABLE Standardisée pour changer la valeur actuelle.

2
Evan Carroll