PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Làm cách nào để kích hoạt trình kích hoạt khi kết thúc chuỗi cập nhật?

Thay vì sử dụng cờ trong report_subscriber chính nó, tôi nghĩ rằng bạn nên sử dụng một hàng đợi các thay đổi đang chờ xử lý riêng biệt. Điều này có một số lợi ích:

  • Không có đệ quy kích hoạt
  • Nâng cao, UPDATE chỉ là DELETE + re- INSERT , do đó, việc chèn vào hàng đợi sẽ thực sự rẻ hơn việc lật cờ
  • Có thể rẻ hơn một chút, vì bạn chỉ cần xếp hàng report_id riêng biệt s, thay vì sao chép toàn bộ report_subscriber bản ghi và bạn có thể làm điều đó trong bảng tạm thời, vì vậy bộ nhớ liền kề và không cần phải đồng bộ hóa gì với đĩa
  • Không phải lo lắng về điều kiện cuộc đua khi lật cờ, vì hàng đợi là cục bộ cho giao dịch hiện tại (trong quá trình triển khai của bạn, các bản ghi bị ảnh hưởng bởi UPDATE report_subscriber không nhất thiết phải là các bản ghi bạn đã chọn trong SELECT ...)

Vì vậy, hãy khởi tạo bảng hàng đợi:

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();

... xếp hàng các thay đổi khi chúng đến, bỏ qua mọi thứ đã được xếp hàng:

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();

... và xử lý hàng đợi ở cuối câu lệnh:

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();

Có một vấn đề nhỏ với điều này:UPDATE không đưa ra bất kỳ đảm bảo nào về thứ tự cập nhật. Điều này có nghĩa là, nếu hai câu lệnh này được chạy đồng thời:

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');

... thì có khả năng xảy ra bế tắc, nếu họ cố gắng cập nhật báo cáo report hồ sơ theo thứ tự ngược lại. Bạn có thể tránh điều này bằng cách thực thi một thứ tự nhất quán cho tất cả các bản cập nhật, nhưng rất tiếc là không có cách nào để đính kèm ORDER BY đến một UPDATE bản tường trình; Tôi nghĩ bạn cần dùng đến con trỏ:

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
$$;

Điều này vẫn có khả năng gây bế tắc nếu khách hàng cố gắng chạy nhiều câu lệnh trong cùng một giao dịch (vì thứ tự cập nhật chỉ được áp dụng trong mỗi câu lệnh, nhưng các khóa cập nhật được giữ cho đến khi cam kết). Bạn có thể giải quyết vấn đề này (loại) bằng cách kích hoạt process_pending_changes() chỉ một lần khi kết thúc giao dịch (hạn chế là trong giao dịch đó, bạn sẽ không thấy các thay đổi của chính mình được phản ánh trong report_subscriber mảng).

Dưới đây là phác thảo chung cho trình kích hoạt "khi cam kết", nếu bạn nghĩ rằng việc điền vào nó là đáng khó khăn:

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. Các tiện ích mở rộng PostgreSQL yêu thích của tôi - Phần thứ hai

  2. Thứ tự PostgreSQL có được đảm bảo đầy đủ nếu sắp xếp theo thuộc tính không phải là duy nhất không?

  3. Cách tạo SELECT COUNT lồng nhau với bí danh trong Postgres

  4. cách tự động tạo bảng dựa trên CSV vào postgres bằng python

  5. Cloud SQL Postgres Không tìm thấy trình điều khiển phù hợp cho jdbc:postgres:// google /