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

La función tarda una eternidad en ejecutarse para una gran cantidad de registros

Lo más probable es que te encuentres con condiciones de carrera . Cuando ejecuta su función 1000 veces en rápida sucesión en transacciones separadas , sucede algo como esto:

T1            T2            T3            ...
SELECT max(id) -- id 1
              SELECT max(id)  -- id 1
                            SELECT max(id)  -- id 1
                                          ...
              Row id 1 locked, wait ...
                            Row id 1 locked, wait ...
UPDATE id 1
                                          ... 

COMMIT
              Wake up, UPDATE id 1 again!
              COMMIT
                            Wake up, UPDATE id 1 again!
                            COMMIT
                                          ... 

En gran parte reescrito y simplificado como función SQL:

CREATE OR REPLACE FUNCTION get_result(val1 text, val2 text)
  RETURNS text AS 
$func$
   UPDATE table t
   SET    id_used = 'Y'
        , col1 = val1
        , id_used_date = now() 
   FROM  (
      SELECT id
      FROM   table 
      WHERE  id_used IS NULL
      AND    id_type = val2
      ORDER  BY id
      LIMIT  1
      FOR    UPDATE   -- lock to avoid race condition! see below ...
      ) t1
   WHERE  t.id_type = val2
   -- AND    t.id_used IS NULL -- repeat condition (not if row is locked)
   AND    t.id = t1.id
   RETURNING  id;
$func$  LANGUAGE sql;

Pregunta relacionada con mucha más explicación:

Explicar

  • No ejecute dos declaraciones SQL separadas. Eso es más caro y amplía el marco de tiempo para las condiciones de carrera. Una UPDATE con una subconsulta es mucho mejor.

  • No necesita PL/pgSQL para la tarea simple. Todavía puedes use PL/pgSQL, el UPDATE permanece igual.

  • Debe bloquear la fila seleccionada para defenderse de las condiciones de carrera. Pero no puede hacer esto con la función agregada que dirige porque, por documentación :

  • Énfasis en negrita mío. Afortunadamente, puedes reemplazar min(id) fácilmente con el equivalente ORDER BY / LIMIT 1 proporcioné arriba. También puede usar un índice.

  • Si la mesa es grande, necesita un índice en id al menos. Asumiendo que id ya está indexado como PRIMARY KEY , Eso podría ayudar. Pero este índice parcial de varias columnas adicional probablemente ayudaría mucho más :

    CREATE INDEX foo_idx ON table (id_type, id)
    WHERE id_used IS NULL;
    

Soluciones alternativas

Bloqueos de aviso Puede ser el enfoque superior aquí:

O es posible que desee bloquear muchas filas a la vez :