sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo hago grandes actualizaciones sin bloqueo en PostgreSQL?

Columna / Fila

... No necesito que se mantenga la integridad transaccional en toda la operación, porque sé que la columna que estoy cambiando no se escribirá ni leerá durante la actualización.

Cualquier UPDATE en el modelo MVCC de PostgreSQL escribe una nueva versión de toda la fila . Si las transacciones simultáneas cambian cualquiera columna de la misma fila, surgen problemas de simultaneidad que consumen mucho tiempo. Detalles en el manual. Conociendo la misma columna no será tocado por transacciones concurrentes evita algunos posibles complicaciones, pero no otras.

Índice

Para evitar ser desviado a una discusión fuera del tema, supongamos que todos los valores de estado para los 35 millones de columnas están configurados actualmente en el mismo valor (no nulo), lo que hace que un índice sea inútil.

Al actualizar la tabla completa (o partes importantes de él) Postgres nunca usa un índice . Un escaneo secuencial es más rápido cuando se deben leer todas o la mayoría de las filas. Por el contrario:el mantenimiento del índice significa un costo adicional para la UPDATE .

Rendimiento

Por ejemplo, digamos que tengo una tabla llamada "pedidos" con 35 millones de filas y quiero hacer esto:

UPDATE orders SET status = null;

Entiendo que está buscando una solución más general (ver más abajo). Pero para abordar la pregunta real preguntó:Esto se puede resolver en cuestión de milisegundos , independientemente del tamaño de la tabla:

ALTER TABLE orders DROP column status
                 , ADD  column status text;

El manual (hasta Postgres 10):

Cuando se agrega una columna con ADD COLUMN , todas las filas existentes en la tabla se inicializan con el valor predeterminado de la columna (NULL si no DEFAULT se especifica la cláusula). Si no hay DEFAULT cláusula, esto es simplemente un cambio de metadatos [...]

El manual (desde Postgres 11):

Cuando se agrega una columna con ADD COLUMN y un DEFAULT no volátil se especifica, el valor predeterminado se evalúa en el momento de la declaración y el resultado se almacena en los metadatos de la tabla. Ese valor se utilizará para la columna de todas las filas existentes. Si no DEFAULT se especifica, se utiliza NULL. En ninguno de los dos casos se requiere una reescritura de la tabla.

Agregar una columna con un volátil DEFAULT o cambiar el tipo de una columna existente requerirá que se reescriba toda la tabla y sus índices. [...]

Y:

La DROP COLUMN form no elimina físicamente la columna, sino que simplemente la hace invisible para las operaciones de SQL. Las operaciones subsiguientes de inserción y actualización en la tabla almacenarán un valor nulo para la columna. Por lo tanto, eliminar una columna es rápido pero no reducirá inmediatamente el tamaño en disco de la tabla, ya que el espacio ocupado por la columna eliminada no se recupera. El espacio se recuperará con el tiempo a medida que se actualicen las filas existentes.

Asegúrese de no tener objetos dependiendo de la columna (restricciones de clave externa, índices, vistas, ...). Tendrías que soltar/recrear esos. Salvo eso, pequeñas operaciones en la tabla de catálogo del sistema pg_attribute Hacer el trabajo. Requiere un bloqueo exclusivo sobre la mesa, lo que puede ser un problema para cargas concurrentes pesadas. (Como enfatiza Buurman en su comentario). Aparte de eso, la operación es cuestión de milisegundos.

Si tiene una columna predeterminada que desea conservar, vuelva a agregarla en un comando separado . Hacerlo en el mismo comando lo aplica a todas las filas inmediatamente. Ver:

  • ¿Agregar nueva columna sin bloqueo de tabla?

Para aplicar realmente el valor predeterminado, considere hacerlo en lotes:

  • ¿PostgreSQL optimiza la adición de columnas con valores PREDETERMINADOS que no sean NULL?

Solución general

dblink ha sido mencionado en otra respuesta. Permite el acceso a bases de datos Postgres "remotas" en conexiones separadas implícitas. La base de datos "remota" puede ser la actual, logrando así "transacciones autónomas" :lo que la función escribe en la base de datos "remota" se confirma y no se puede revertir.

Esto permite ejecutar una sola función que actualiza una tabla grande en partes más pequeñas y cada parte se confirma por separado. Evita la acumulación de gastos generales de transacción para un gran número de filas y, lo que es más importante, libera bloqueos después de cada parte. Esto permite que las operaciones simultáneas se lleven a cabo sin mucha demora y reduce la probabilidad de interbloqueos.

Si no tiene acceso simultáneo, esto no es útil, excepto para evitar ROLLBACK después de una excepción. Considere también SAVEPOINT para ese caso.

Descargo de responsabilidad

En primer lugar, muchas transacciones pequeñas son en realidad más caras. Esto solo tiene sentido para mesas grandes . El punto óptimo depende de muchos factores.

Si no está seguro de lo que está haciendo:una sola transacción es el método seguro . Para que esto funcione correctamente, las operaciones concurrentes en la mesa deben funcionar. Por ejemplo:escrituras simultáneas puede mover una fila a una partición que supuestamente ya está procesada. O las lecturas simultáneas pueden ver estados intermedios inconsistentes. Ha sido advertido.

Instrucciones paso a paso

El módulo adicional dblink debe instalarse primero:

  • ¿Cómo usar (instalar) dblink en PostgreSQL?

La configuración de la conexión con dblink depende en gran medida de la configuración de su clúster de base de datos y de las políticas de seguridad vigentes. Puede ser complicado. Respuesta posterior relacionada con más cómo conectarse con dblink :

  • Inserciones persistentes en una UDF incluso si la función aborta

Crear un FOREIGN SERVER y un USER MAPPING como se indica allí para simplificar y agilizar la conexión (a menos que ya tenga una).
Suponiendo una serial PRIMARY KEY con o sin algunos huecos.

CREATE OR REPLACE FUNCTION f_update_in_steps()
  RETURNS void AS
$func$
DECLARE
   _step int;   -- size of step
   _cur  int;   -- current ID (starting with minimum)
   _max  int;   -- maximum ID
BEGIN
   SELECT INTO _cur, _max  min(order_id), max(order_id) FROM orders;
                                        -- 100 slices (steps) hard coded
   _step := ((_max - _cur) / 100) + 1;  -- rounded, possibly a bit too small
                                        -- +1 to avoid endless loop for 0
   PERFORM dblink_connect('myserver');  -- your foreign server as instructed above

   FOR i IN 0..200 LOOP                 -- 200 >> 100 to make sure we exceed _max
      PERFORM dblink_exec(
       $$UPDATE public.orders
         SET    status = 'foo'
         WHERE  order_id >= $$ || _cur || $$
         AND    order_id <  $$ || _cur + _step || $$
         AND    status IS DISTINCT FROM 'foo'$$);  -- avoid empty update

      _cur := _cur + _step;

      EXIT WHEN _cur > _max;            -- stop when done (never loop till 200)
   END LOOP;

   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;

Llamar:

SELECT f_update_in_steps();

Puede parametrizar cualquier parte según sus necesidades:el nombre de la tabla, el nombre de la columna, el valor... solo asegúrese de desinfectar los identificadores para evitar la inyección SQL:

  • Nombre de tabla como parámetro de función de PostgreSQL

Evite las ACTUALIZACIONES vacías:

  • ¿Cómo puedo (o puedo) SELECCIONAR DISTINTO en varias columnas?