As peculiaridades dos mecanismos internos de PostgreSQL permiten que sexa moi rápido nalgunhas situacións e "non moi rápido" noutras. Hoxe centrarémonos nun exemplo clásico dun conflito entre como funciona un DBMS e o que fai o desenvolvedor con el: ACTUALIZACIÓN vs principios MVCC.
Breve historia de
Cando se modifica unha fila mediante un comando UPDATE, realízanse dúas operacións: DELETE e INSERT. EN versión actual da cadea xmax establécese igual ao número da transacción que realizou a ACTUALIZACIÓN. Despois créase unha nova versión a mesma liña; o seu valor xmin coincide co valor xmax da versión anterior.
Algún tempo despois de que se complete esta transacción, a versión antiga ou nova, dependendo de COMMIT/ROOLBACK
, será recoñecido "morto" (tuplas mortas) ao pasar VACUUM
segundo a táboa e despexado.
Pero isto non ocorrerá de inmediato, pero os problemas cos "mortos" pódense adquirir moi rapidamente - con repetidas ou
#1: Gústame movelo
Digamos que o teu método está a traballar na lóxica empresarial e, de súpeto, dáse conta de que sería necesario actualizar o campo X nalgún rexistro:
UPDATE tbl SET X = <newX> WHERE pk = $1;
Entón, a medida que avanza a execución, resulta que o campo Y tamén debe actualizarse:
UPDATE tbl SET Y = <newY> WHERE pk = $1;
... e despois tamén Z - por que perder o tempo en bagatelas?
UPDATE tbl SET Z = <newZ> WHERE pk = $1;
Cantas versións deste rexistro temos agora na base de datos? Si, 4 pezas! Deles, un é relevante e 3 terás que limpar despois de ti mediante [auto]VACUUM.
Non o fagas deste xeito! Use actualizando todos os campos nunha única solicitude - case sempre a lóxica do método pódese cambiar así:
UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;
#2: O uso é distinto de, Luke!
Entón, aínda querías
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;
Unha solicitude de aproximadamente este formulario ocorre con bastante frecuencia e case sempre non para cubrir un novo campo baleiro, senón para corrixir algúns erros nos datos. Ao mesmo tempo, ela mesma non se ten en conta en absoluto a corrección dos datos existentes - pero en balde! É dicir, o rexistro está reescrito, aínda que contiña exactamente o que se quería, pero por que? Imos corrixilo:
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;
Moitas persoas non son conscientes da existencia dun operador tan marabilloso, así que aquí tes unha folla de trucos IS DISTINCT FROM
e outros operadores lóxicos para axudar:
... e un pouco sobre operacións en complexo ROW()
-expresións:
# 3: Recoñezo á miña querida por... bloqueo
están sendo postos en marcha dous procesos paralelos idénticos, cada un dos cales tenta marcar a entrada que está "en curso":
UPDATE tbl SET processing = TRUE WHERE pk = $1;
Aínda que estes procesos realmente fagan cousas independentes entre si, pero dentro do mesmo ID, o segundo cliente estará "bloqueado" nesta solicitude ata que se complete a primeira transacción.
Solución # 1: a tarefa redúcese á anterior
Engadímolo de novo IS DISTINCT FROM
:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;
Neste formulario, a segunda solicitude simplemente non cambiará nada na base de datos, xa está todo como debería ser; polo tanto, non se producirá o bloqueo. A continuación, procesamos o feito de "non atopar" o rexistro no algoritmo aplicado.
Solución # 2: peches de asesoramento
Un gran tema para un artigo separado, no que podes ler
Solución # 3: chamadas estúpidas
Pero isto é exactamente o que debería pasarche traballo simultáneo co mesmo rexistro? Ou estropeou os algoritmos para chamar á lóxica empresarial no lado do cliente, por exemplo? E se o pensas?...
Fonte: www.habr.com