Parmi les nombreuses choses que nous faisons avec Postgres au travail, nous l'utilisons comme cache pour certains types de requêtes distantes. Notre schéma est:Comment deux requêtes DELETE peuvent-elles être bloquées dans Postgres?
CREATE TABLE IF NOT EXISTS cache (
key VARCHAR(256) PRIMARY KEY,
value TEXT NOT NULL,
ttl TIMESTAMP DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_cache_ttl ON cache(ttl);
Cette table ne comporte pas de déclencheurs ou de clés étrangères. Les mises à jour sont généralement:
INSERT INTO cache (key, value, ttl)
VALUES ('Ethan is testing8393645', '"hi6286166"', sec2ttl(300))
ON CONFLICT (key) DO UPDATE
SET value = '"hi6286166"', ttl = sec2ttl(300);
(Où sec2ttl
est défini comme :)
CREATE OR REPLACE FUNCTION sec2ttl(seconds FLOAT)
RETURNS TIMESTAMP AS $$
BEGIN
IF seconds IS NULL THEN
RETURN NULL;
END IF;
RETURN now() + (seconds || ' SECOND')::INTERVAL;
END;
$$ LANGUAGE plpgsql;
Interrogation le cache se fait dans une transaction comme celle-ci:
BEGIN;
DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;
SELECT value FROM cache WHERE key = 'Ethan is testing6460437';
COMMIT;
Il y a quelques choses à ne pas Aime à propos de cette conception - le DELETE
passe dans le cache "lit", l'index sur (edit: ASC est la valeur par défaut, merci wargre!) plus le fait que nous utilisons Postgres comme cache du tout. Mais tout cela aurait été acceptable, sauf que nous avons commencé à recevoir la production, les blocages qui ont tendance à ressembler à ceci:cache.ttl
n'est pas ascendant ce qui le rend un peu usele ss,
ERROR: deadlock detected
DETAIL: Process 12750 waits for ShareLock on transaction 632693475; blocked by process 10080.
Process 10080 waits for ShareLock on transaction 632693479; blocked by process 12750.
HINT: See server log for query details.
CONTEXT: while deleting tuple (426,1) in relation "cache"
[SQL: 'DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;']
analysant les journaux plus indique bien que les deux transactions effectuaient cette opération DELETE
.
Pour autant que je peux dire:
- Mes transactions sont en
READ COMMITTED
mode d'isolation. - ShareLocks sont saisis par une transaction pour indiquer qu'elle veut muter des lignes qu'une autre transaction a mutées (c'est-à-dire verrouillées).
- En fonction de la sortie d'une requête
EXPLAIN
, les ShareLocks doivent être saisis par les deux transactionsDELETE
dans l'ordre physique. - Le blocage indique que les deux requêtes verrouillent les lignes dans un ordre différent.
Si tout est correct, alors une transaction simultanée a changé l'ordre physique des lignes. Je vois qu'un UPDATE
peut déplacer une rangée vers une position physique plus tôt ou plus tard, mais dans mon application, le UPDATE
s enlève toujours des rangées par les DELETE
s (parce qu'elles prolongent toujours la durée de vie d'une ligne). Si les lignes étaient auparavant dans l'ordre physique et que vous en supprimez une, il vous reste l'ordre physique. De même pour DELETE
. Nous ne faisons aucune VACUUM
ou toute autre opération que vous pourriez vous attendre à réorganiser les lignes.
Basé sur Avoiding PostgreSQL deadlocks when performing bulk update and delete operations, j'ai essayé de changer les DELETE
questions à:
DELETE FROM cache c
USING (
SELECT key
FROM cache
WHERE ttl IS NOT NULL AND now() > ttl
ORDER BY ttl ASC
FOR UPDATE
) del
WHERE del.key = c.key;
Cependant, je suis toujours en mesure d'obtenir localement des blocages. Donc, généralement, comment deux requêtes DELETE
peuvent-elles être bloquées? Est-ce parce qu'ils se verrouillent dans un ordre indéfini, et si oui, comment appliquer un ordre spécifique?
Qu'en est-il de la gestion de votre cache dans une connexion avec autocommit et sans transaction? (btw, l'index est correct, il est utilisé par votre dlete) – wargre
Je pense que même si je n'ai pas de transaction, Postgres crée une transaction autour de chaque requête, n'est-ce pas? Et les DELETE semblent se bloquer par eux-mêmes .. (BTW, merci, je vois ASC est la valeur par défaut pour les indices.) – Ethan
Le point de la 'SELECT ... FOR UPDATE' est d'appliquer un ordre globalement cohérent pour toutes les acquisitions de serrures . Si deux valeurs 'ttl' coïncident, l'ordre des lignes n'est pas défini et si une valeur' ttl' est mise à jour, alors l'ordre, bien défini, peut différer entre les transactions concurrentes. Essayez 'ORDER BY key' à la place. –