PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Bagaimana saya bisa memicu pemicu di akhir rantai pembaruan?

Daripada menggunakan tanda di report_subscriber sendiri, saya pikir Anda akan lebih baik dengan antrian terpisah dari perubahan yang tertunda. Ini memiliki beberapa manfaat:

  • Tidak ada pemicu rekursi
  • Di bawah tenda, UPDATE hanya DELETE + ulang INSERT , jadi memasukkan ke dalam antrian sebenarnya akan lebih murah daripada membalik bendera
  • Mungkin sedikit lebih murah, karena Anda hanya perlu mengantrekan report_id yang berbeda s, daripada mengkloning seluruh report_subscriber record, dan Anda dapat melakukannya dalam tabel temp, sehingga penyimpanannya berdekatan dan tidak ada yang perlu disinkronkan ke disk
  • Tidak ada kondisi balapan yang perlu dikhawatirkan saat membalik tanda, karena antreannya bersifat lokal untuk transaksi saat ini (dalam implementasi Anda, catatan dipengaruhi oleh UPDATE report_subscriber belum tentu catatan yang sama yang Anda ambil di SELECT ...)

Jadi, inisialisasi tabel antrian:

CREATE FUNCTION create_queue_table() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  CREATE TEMP TABLE pending_subscriber_changes(report_id INT UNIQUE) ON COMMIT DROP;
  RETURN NULL;
END
$$;

CREATE TRIGGER create_queue_table_if_not_exists
  BEFORE INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH STATEMENT
  WHEN (to_regclass('pending_subscriber_changes') IS NULL)
  EXECUTE PROCEDURE create_queue_table();

...mengantrekan perubahan saat mereka tiba, mengabaikan apa pun yang sudah antri:

CREATE FUNCTION queue_subscriber_change() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  IF TG_OP IN ('DELETE', 'UPDATE') THEN
    INSERT INTO pending_subscriber_changes (report_id) VALUES (old.report_id)
    ON CONFLICT DO NOTHING;
  END IF;

  IF TG_OP IN ('INSERT', 'UPDATE') THEN
    INSERT INTO pending_subscriber_changes (report_id) VALUES (new.report_id)
    ON CONFLICT DO NOTHING;
  END IF;
  RETURN NULL;
END
$$;

CREATE TRIGGER queue_subscriber_change
  AFTER INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH ROW
  EXECUTE PROCEDURE queue_subscriber_change();

...dan memproses antrian di akhir pernyataan:

CREATE FUNCTION process_pending_changes() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  UPDATE report
  SET report_subscribers = ARRAY(
    SELECT DISTINCT subscriber_name
    FROM report_subscriber s
    WHERE s.report_id = report.report_id
    ORDER BY subscriber_name
  )
  FROM pending_subscriber_changes c
  WHERE report.report_id = c.report_id;

  DROP TABLE pending_subscriber_changes;
  RETURN NULL;
END
$$;

CREATE TRIGGER process_pending_changes
  AFTER INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH STATEMENT
  EXECUTE PROCEDURE process_pending_changes();

Ada sedikit masalah dengan ini:UPDATE tidak menawarkan jaminan apa pun tentang urutan pembaruan. Artinya, jika kedua pernyataan ini dijalankan secara bersamaan:

INSERT INTO report_subscriber (report_id, subscriber_name) VALUES (1, 'a'), (2, 'b');
INSERT INTO report_subscriber (report_id, subscriber_name) VALUES (2, 'x'), (1, 'y');

...maka ada kemungkinan kebuntuan, jika mereka mencoba memperbarui report catatan dalam urutan yang berlawanan. Anda dapat menghindari ini dengan menerapkan urutan yang konsisten untuk semua pembaruan, tetapi sayangnya tidak ada cara untuk melampirkan ORDER BY ke UPDATE penyataan; Saya pikir Anda perlu menggunakan kursor:

CREATE FUNCTION process_pending_changes() RETURNS TRIGGER LANGUAGE plpgsql AS $$
DECLARE
  target_report CURSOR FOR
    SELECT report_id
    FROM report
    WHERE report_id IN (TABLE pending_subscriber_changes)
    ORDER BY report_id
    FOR NO KEY UPDATE;
BEGIN
  FOR target_record IN target_report LOOP
    UPDATE report
    SET report_subscribers = ARRAY(
        SELECT DISTINCT subscriber_name
        FROM report_subscriber
        WHERE report_id = target_record.report_id
        ORDER BY subscriber_name
      )
    WHERE CURRENT OF target_report;
  END LOOP;

  DROP TABLE pending_subscriber_changes;
  RETURN NULL;
END
$$;

Ini masih berpotensi menemui jalan buntu jika klien mencoba menjalankan beberapa pernyataan dalam transaksi yang sama (karena pemesanan pembaruan hanya diterapkan dalam setiap pernyataan, tetapi penguncian pembaruan ditahan hingga komit). Anda dapat mengatasi ini (semacam) dengan menembakkan process_pending_changes() sekali saja di akhir transaksi (kekurangannya adalah, dalam transaksi itu, Anda tidak akan melihat perubahan Anda sendiri yang tercermin dalam report_subscribers susunan).

Berikut adalah garis besar umum untuk pemicu "saat komit", jika menurut Anda perlu repot untuk mengisinya:

CREATE FUNCTION run_on_commit() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  <your code goes here>
  RETURN NULL;
END
$$;

CREATE FUNCTION trigger_already_fired() RETURNS BOOLEAN LANGUAGE plpgsql VOLATILE AS $$
DECLARE
  already_fired BOOLEAN;
BEGIN
  already_fired := NULLIF(current_setting('my_vars.trigger_already_fired', TRUE), '');
  IF already_fired IS TRUE THEN
    RETURN TRUE;
  ELSE
    SET LOCAL my_vars.trigger_already_fired = TRUE;
    RETURN FALSE;
  END IF;
END
$$;

CREATE CONSTRAINT TRIGGER my_trigger
  AFTER INSERT OR UPDATE OR DELETE ON my_table
  DEFERRABLE INITIALLY DEFERRED
  FOR EACH ROW
  WHEN (NOT trigger_already_fired())
  EXECUTE PROCEDURE run_on_commit();



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cara membuat daftar semua database menggunakan PostgreSQL

  2. Membuat kueri untuk membuat set kueri Django yang menantang

  3. Mengapa postgres tidak menggunakan indeks dalam kueri saya

  4. Apakah VIEWS PostgreSQL dibuat baru setiap kali mereka ditanyai?

  5. Django ORM meninggalkan koneksi idle di Postgres DB