report_subscriber
. में ध्वज का उपयोग करने के बजाय स्वयं, मुझे लगता है कि आप लंबित परिवर्तनों की एक अलग कतार के साथ बेहतर होंगे। इसके कुछ लाभ हैं:
- कोई ट्रिगर रिकर्सन नहीं
- हुड के तहत,
UPDATE
बस हैDELETE
+ पुनः-INSERT
, इसलिए एक कतार में सम्मिलित करना वास्तव में झंडे को फहराने की तुलना में सस्ता होगा - संभवतः काफी सस्ता है, क्योंकि आपको केवल विशिष्ट
report_id
को कतार में लगाना होगा s, संपूर्णreport_subscriber
. को क्लोन करने के बजाय रिकॉर्ड, और आप इसे एक अस्थायी तालिका में कर सकते हैं, इसलिए भंडारण सन्निहित है और डिस्क के साथ कुछ भी समन्वयित करने की आवश्यकता नहीं है - झंडे को फ़्लिप करते समय चिंता करने की कोई दौड़ की स्थिति नहीं है, क्योंकि कतार वर्तमान लेन-देन के लिए स्थानीय है (आपके कार्यान्वयन में,
UPDATE report_subscriber
द्वारा प्रभावित रिकॉर्ड्स जरूरी नहीं कि वे वही रिकॉर्ड हों जिन्हें आपनेSELECT
. में उठाया था ...)
तो, क्यू टेबल को इनिशियलाइज़ करें:
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();
...पहुंचते ही परिवर्तनों को कतारबद्ध करें, पहले से पंक्तिबद्ध किसी भी चीज़ को अनदेखा करें:
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();
...और स्टेटमेंट के अंत में क्यू को प्रोसेस करें:
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();
इसमें थोड़ी समस्या है:UPDATE
अद्यतन आदेश के बारे में कोई गारंटी नहीं देता है। इसका मतलब यह है कि, अगर इन दोनों बयानों को एक साथ चलाया गया:
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');
...तो एक गतिरोध की संभावना है, अगर वे report
. को अपडेट करने का प्रयास करते हैं विपरीत क्रम में रिकॉर्ड। आप सभी अपडेट के लिए एक सुसंगत आदेश लागू करके इससे बच सकते हैं, लेकिन दुर्भाग्य से ORDER BY
संलग्न करने का कोई तरीका नहीं है एक UPDATE
. के लिए बयान; मुझे लगता है कि आपको कर्सर का सहारा लेना होगा:
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
$$;
यह अभी भी गतिरोध की क्षमता रखता है यदि क्लाइंट एक ही लेन-देन के भीतर कई कथनों को चलाने का प्रयास करता है (क्योंकि अद्यतन आदेश केवल प्रत्येक कथन के भीतर लागू होता है, लेकिन अद्यतन लॉक प्रतिबद्ध होने तक आयोजित किए जाते हैं)। आप process_pending_changes()
. को बंद करके इस (प्रकार) के आसपास काम कर सकते हैं लेन-देन के अंत में केवल एक बार (दोष यह है कि, उस लेन-देन के भीतर, आप report_subscribers
में अपने स्वयं के परिवर्तन नहीं देखेंगे। सरणी)।
अगर आपको लगता है कि इसे भरना मुश्किल है, तो "ऑन कमिट" ट्रिगर के लिए यहां एक सामान्य रूपरेखा दी गई है:
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();