PostgreSQL के साथ प्रति पंक्ति एक अद्वितीय काउंटर कैसे रखें?


10

मुझे डॉक्यूमेंट_सेविज़न टेबल में एक यूनीक (प्रति-पंक्ति) रिविजन नंबर रखने की आवश्यकता है, जहां रिवीजन नंबर को एक डॉक्यूमेंट में रखा जाता है, इसलिए यह केवल संबंधित डॉक्यूमेंट के लिए, पूरे टेबल के लिए यूनिक नहीं है।

मैं शुरू में कुछ इस तरह से आया था:

current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);

लेकिन एक दौड़ की स्थिति है!

मैं इसके साथ हल करने की कोशिश कर रहा हूं pg_advisory_lock, लेकिन प्रलेखन थोड़ा दुर्लभ है और मैं इसे पूरी तरह से नहीं समझता, और मैं गलती से कुछ लॉक नहीं करना चाहता।

निम्नलिखित स्वीकार्य है, या क्या मैं इसे गलत कर रहा हूं, या इसका कोई बेहतर समाधान है?

SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);

क्या मुझे दिए गए ऑपरेशन (की 2) के लिए दस्तावेज़ पंक्ति (की 1) को बंद नहीं करना चाहिए? तो यह उचित समाधान होगा:

SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;

शायद मैं PostgreSQL और एक सीरियल के लिए इस्तेमाल नहीं किया जा सकता है, या शायद एक अनुक्रम है और nextval()काम बेहतर होगा?


मुझे समझ में नहीं आ रहा है कि "दिए गए ऑपरेशन के लिए" और आपके पास "की 2" कहां से आया है।
ट्राईवेग लॉगस्टोएल

2
यदि आप निराशावादी लॉकिंग चाहते हैं तो आपकी लॉकिंग रणनीति ठीक है, लेकिन मैं pg_advisory_xact_lock का उपयोग करूंगा ताकि सभी लॉक स्वचालित रूप से COMMIT / ROLLBACK पर जारी हो जाएं।
Trygve Laugstøl

जवाबों:


2

मान लें कि आप दस्तावेज़ के सभी संशोधनों को एक तालिका में संग्रहीत करते हैं , एक दृष्टिकोण संशोधन संख्या को संग्रहीत नहीं करना होगा, लेकिन तालिका में संग्रहीत संशोधनों की संख्या के आधार पर इसकी गणना करें।

यह अनिवार्य रूप से, एक व्युत्पन्न मूल्य है, ऐसा कुछ नहीं जिसे आपको संग्रहीत करने की आवश्यकता है।

एक विंडो फ़ंक्शन का उपयोग संशोधन संख्या की गणना करने के लिए किया जा सकता है, जैसे कुछ

row_number() over (partition by document_id order by <change_date>)

और आपको change_dateसंशोधन के आदेश का ट्रैक रखने के लिए कुछ कॉलम की आवश्यकता होगी ।


दूसरी तरफ, यदि आपके पास revisionदस्तावेज़ की संपत्ति है और यह इंगित करता है कि "दस्तावेज़ कितनी बार बदल गया है", तो मैं आशावादी लॉकिंग दृष्टिकोण के लिए जाऊंगा, कुछ इस तरह:

update documents
set revision = revision + 1
where document_id = <id> and revision = <old_revision>;

यदि यह 0 पंक्तियों को अद्यतन करता है, तो मध्यवर्ती अद्यतन हो गया है और आपको इसके उपयोगकर्ता को सूचित करने की आवश्यकता है।


सामान्य तौर पर, अपने समाधान को यथासंभव सरल रखने का प्रयास करें। इस मामले में द्वारा

  • जब तक बिल्कुल आवश्यक न हो, स्पष्ट लॉकिंग कार्यों के उपयोग से बचें
  • कम डेटाबेस ऑब्जेक्ट्स (प्रति दस्तावेज़ अनुक्रम नहीं) और कम विशेषताएँ संग्रहीत करना (संशोधन की गणना न करें यदि यह गणना की जा सकती है)
  • एक या updateएक के selectबाद एक के बजाय एक बयान का उपयोग insertकरupdate

वास्तव में, मुझे मूल्य को संग्रहीत करने की आवश्यकता नहीं है जब यह गणना की जा सकती है। मुझे याद दिलाने के लिये धन्यवाद!
जूलियन पोर्टलियर

2
दरअसल, मेरे संदर्भ में, पुराने संशोधन कुछ बिंदु पर हटा दिए जाएंगे, इसलिए मैं इसकी गणना नहीं कर सकता या संशोधन संख्या कम हो जाएगी :)
जूलियन पोर्टलियर

3

अनुक्रम अद्वितीय होने की गारंटी है, और यदि आपके दस्तावेज़ों की संख्या बहुत अधिक नहीं है, तो आपका उपयोग-मामला लागू होता है (अन्यथा आपके पास प्रबंधित करने के लिए बहुत सारे अनुक्रम हैं)। अनुक्रम द्वारा उत्पन्न मान प्राप्त करने के लिए RETURNING खंड का उपयोग करें। उदाहरण के लिए, 'A36' को document_id के रूप में उपयोग करना:

  • दस्तावेज़ के अनुसार, आप वेतन वृद्धि को ट्रैक करने के लिए एक अनुक्रम बना सकते हैं।
  • दृश्यों का प्रबंधन कुछ देखभाल के साथ करने की आवश्यकता होगी। आप संभवतः एक अलग तालिका रख सकते हैं जिसमें दस्तावेज़ के नाम और उस से जुड़े अनुक्रम document_idको document_revisionsतालिका को सम्मिलित / अद्यतन करते समय संदर्भ के लिए रखा जा सकता है।

     CREATE SEQUENCE d_r_document_a36_seq;
    
     INSERT INTO document_revisions (document_id, rev)
     VALUES ('A36',nextval('d_r_document_a36_seq')) RETURNING rev;
    

फ़ॉर्मेटिंग डेज़ो के लिए धन्यवाद, मैंने ध्यान नहीं दिया कि जब मैं अपनी टिप्पणियों में पेस्ट करता था तो कितना बुरा लगता था।
bma

एक अनुक्रम एक बुरा काउंटर है यदि आप चाहते हैं कि अगला मूल्य पिछले + 1 हो, क्योंकि वे लेनदेन के भीतर नहीं चलते हैं।
Trygve Laugstøl

1
एह? अनुक्रम परमाणु हैं। इसलिए मैंने प्रति दस्तावेज एक अनुक्रम सुझाया । उन्हें गैप-मुक्त होने की भी गारंटी नहीं है, क्योंकि रोलबैक के बाद यह बढ़ने के बाद अनुक्रम को बढ़ाता नहीं है। मैं यह नहीं कह रहा हूं कि उचित लॉकिंग एक अच्छा समाधान नहीं है, केवल यही क्रम एक विकल्प प्रस्तुत करता है।
bma

1
धन्यवाद! अनुक्रम निश्चित रूप से जाने का तरीका है अगर मुझे संशोधन संख्या को संग्रहीत करने की आवश्यकता है।
जूलियन पोर्टलियर

2
ध्यान दें कि भारी मात्रा में अनुक्रम प्रदर्शन पर एक बड़ी हिट है, क्योंकि एक अनुक्रम अनिवार्य रूप से एक पंक्ति के साथ एक तालिका है। आप यहाँ
मैग्नस

2

यह अक्सर आशावादी लॉकिंग के साथ हल किया जाता है:

SELECT version, x FROM foo;

version | foo
    123 | ..

UPDATE foo SET x=?, version=124 WHERE version=123

यदि अद्यतन 0 पंक्तियों को अद्यतन करता है, तो आपने अपना अद्यतन याद कर लिया है क्योंकि कोई अन्य पहले से ही पंक्ति को अद्यतन करता है।


धन्यवाद! यह एक अच्छा है जब आपको किसी दस्तावेज़ पर अपडेट का एक काउंटर रखने की आवश्यकता होती है! लेकिन मुझे document_revisions तालिका में प्रत्येक पंक्तियों के लिए एक अद्वितीय संशोधन संख्या की आवश्यकता है, जिसे अपडेट नहीं किया जाएगा, और पिछले संशोधन (यानी पिछली पंक्ति + 1 की संशोधन संख्या) का अनुयायी होना चाहिए।
जूलियन पोर्टलियर

1
हम्म, आप इस तकनीक का उपयोग क्यों नहीं कर सकते हैं? यह एकमात्र तरीका है (निराशावादी लॉकिंग के अलावा) जो आपको अंतराल-कम अनुक्रम देगा।
ट्राईवे लागास्टोएल

2

(इस विषय के बारे में एक लेख को फिर से खोजने की कोशिश करते समय मुझे यह सवाल आया था। अब जब मैंने इसे पा लिया है, तो मैं इसे यहाँ पोस्ट कर रहा हूँ अगर अन्य लोग वर्तमान में चुने गए उत्तर के वैकल्पिक विकल्प की खोज में हैं - row_number())

मेरे पास यही उपयोग मामला है। हमारे सास में एक विशिष्ट परियोजना में डाले गए प्रत्येक रिकॉर्ड के लिए हमें एक अद्वितीय, वृद्धि संख्या की आवश्यकता होती है जो समवर्ती INSERTएस के चेहरे में उत्पन्न हो सकती है और आदर्श रूप से अंतरहीन है।

यह लेख एक अच्छे समाधान का वर्णन करता है , जिसे मैं यहाँ आसानी और संक्षिप्तता के लिए संक्षेप में प्रस्तुत करूँगा।

  1. एक अलग तालिका है जो अगले मूल्य प्रदान करने के लिए काउंटर के रूप में कार्य करती है। इसमें दो कॉलम होंगे, document_idऔर countercounterहो जाएगा DEFAULT 0वैकल्पिक रूप से, अगर आप पहले से ही एक है documentइकाई है कि समूहों सभी संस्करणों, एक counterवहाँ जोड़ा जा सकता है।
  2. तालिका में एक BEFORE INSERTट्रिगर जोड़ें document_versionsजो परमाणु रूप से काउंटर बढ़ाता है ( UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter) और फिर NEW.versionउस काउंटर मान पर सेट होता है।

वैकल्पिक रूप से, आप आवेदन परत पर ऐसा करने के लिए एक सीटीई का उपयोग करने में सक्षम हो सकते हैं (हालांकि मैं इसे प्राथमिकता के लिए ट्रिगर होना पसंद करता हूं):

WITH version AS (
  UPDATE document_revision_counters
    SET counter = counter + 1 
    WHERE document_id = 1
    RETURNING counter
)

INSERT 
  INTO document_revisions (document_id, rev, other_data)
  SELECT 1, version.counter, 'some other data'
  FROM "version";

यह सिद्धांत में समान है कि आप इसे शुरू में हल करने के लिए कैसे प्रयास कर रहे थे, सिवाय इसके कि एक बयान में एक काउंटर पंक्ति को संशोधित करके यह तब तक बासी मूल्य के रीड को ब्लॉक करता INSERTहै जब तक कि यह प्रतिबद्ध नहीं है।

psqlइस कार्रवाई में इसे दिखाने से एक प्रतिलेख है :

scratch=# CREATE TABLE document_revisions (document_id integer, rev integer, other_data text, PRIMARY KEY (document_id, rev));
CREATE TABLE

scratch=# CREATE TABLE document_revision_counters (document_id integer PRIMARY KEY, counter integer DEFAULT 0);
CREATE TABLE

scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 1 v1'
    FROM "version";
INSERT 0 1

scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 1 v2'
    FROM "version";
INSERT 0 1

scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 2 v1'
    FROM "version";
INSERT 0 1

scratch=# SELECT * FROM document_revisions;
 document_id | rev | other_data 
-------------+-----+------------
           2 |   1 | doc 1 v1
           2 |   2 | doc 1 v2
           2 |   1 | doc 2 v1
(3 rows)

जैसा कि आप देख सकते हैं, आपको सावधान रहना INSERTहोगा कि कैसे होता है, इसलिए ट्रिगर संस्करण, जो इस तरह दिखता है:

CREATE OR REPLACE FUNCTION set_doc_revision()
RETURNS TRIGGER AS $$ BEGIN
  WITH version AS (
    INSERT INTO document_revision_counters (document_id, counter) VALUES (NEW.document_id, 1)
    ON CONFLICT (document_id)
    DO UPDATE SET counter = document_revision_counters.counter + 1
    RETURNING counter
  )

  SELECT INTO NEW.rev counter FROM version; RETURN NEW; END;
$$ LANGUAGE 'plpgsql';

CREATE TRIGGER set_doc_revision BEFORE INSERT ON document_revisions
FOR EACH ROW EXECUTE PROCEDURE set_doc_revision();

यह INSERTबहुत अधिक सीधे आगे बनाता है और डेटा की अखंडता INSERTमनमाने स्रोतों से उत्पन्न होने के कारण अधिक मजबूत होती है:

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'baz');
INSERT 0 1

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'foo');
INSERT 0 1

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'bar');
INSERT 0 1

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (42, 'meaning of life');
INSERT 0 1

scratch=# SELECT * FROM document_revisions;
 document_id | rev |   other_data    
-------------+-----+-----------------
           1 |   1 | baz
           1 |   2 | foo
           1 |   3 | bar
          42 |   1 | meaning of life
(4 rows)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.