PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Funzione che impiega un'eternità per l'esecuzione per un numero elevato di record

Molto probabilmente ti stai imbattendo in condizioni di gara . Quando esegui la tua funzione 1000 volte in rapida successione in transazioni separate , succede qualcosa del genere:

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
                                          ... 

In gran parte riscritto e semplificato come funzione 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;

Domanda correlata con molte più spiegazioni:

Spiega

  • Non eseguire due istruzioni SQL separate. Questo è più costoso e amplia il periodo di tempo per le condizioni di gara. Un UPDATE con una sottoquery è molto meglio.

  • Non è necessario PL/pgSQL per l'attività semplice. Puoi ancora puoi usa PL/pgSQL, il UPDATE rimane lo stesso.

  • Devi bloccare la riga selezionata per difenderti dalle condizioni di gara. Ma non puoi farlo con la funzione di aggregazione che dirigi perché, per documentazione :

  • Enfasi in grassetto mio. Fortunatamente, puoi sostituire min(id) facilmente con l'equivalente ORDER BY / LIMIT 1 Ho fornito sopra. Può usare anche un indice.

  • Se il tavolo è grande, è necessario un indice su id almeno. Supponendo che id è già indicizzato come PRIMARY KEY , questo aiuterebbe. Ma questo ulteriore indice parziale multicolonna probabilmente aiuterebbe molto di più :

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

Soluzioni alternative

Blocchi di avviso Potrebbe essere l'approccio superiore qui:

Oppure potresti voler bloccare più righe contemporaneamente :