PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Die Ausführung der Funktion für eine große Anzahl von Datensätzen dauert ewig

Höchstwahrscheinlich geraten Sie in Rennbedingungen . Wenn Sie Ihre Funktion 1000 Mal in schneller Folge in separaten Transaktionen ausführen , passiert so etwas:

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

Weitgehend umgeschrieben und vereinfacht als SQL-Funktion:

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;

Verwandte Frage mit viel mehr Erklärung:

Erklären

  • Führen Sie nicht zwei separate SQL-Anweisungen aus. Das ist teurer und erweitert den Zeitrahmen für Rennbedingungen. Ein UPDATE mit einer Unterabfrage ist viel besser.

  • Für die einfache Aufgabe benötigen Sie kein PL/pgSQL. Sie können immer noch Verwenden Sie PL/pgSQL, das UPDATE bleibt gleich.

  • Sie müssen die ausgewählte Reihe sperren, um sich gegen Rennbedingungen zu verteidigen. Aber Sie können dies nicht mit der von Ihnen geleiteten Aggregatfunktion tun, weil pro Dokumentation :

  • Fette Hervorhebung von mir. Glücklicherweise können Sie min(id) ersetzen ganz einfach mit dem entsprechenden ORDER BY / LIMIT 1 habe ich oben angegeben. Kann genauso gut einen Index verwenden.

  • Wenn der Tisch groß ist, brauchen Sie ein Index auf id wenigstens. Angenommen, diese id ist bereits als PRIMARY KEY indiziert , das würde helfen. Aber dieser zusätzliche teilweise mehrspaltige Index würde wahrscheinlich sehr viel helfen :

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

Alternative Lösungen

Hinweissperren Kann hier der überlegene Ansatz sein:

Oder Sie möchten vielleicht viele Zeilen gleichzeitig sperren :