PostgreSQL
 sql >> डेटाबेस >  >> RDS >> PostgreSQL

एक समारोह में चयन या INSERT दौड़ की स्थिति के लिए प्रवण है?

यह SELECT . की बार-बार होने वाली समस्या है या INSERT संभावित समवर्ती लेखन भार के तहत, UPSERT . से संबंधित (लेकिन इससे भिन्न) (जो INSERT . है या UPDATE )।

यह PL/pgSQL फ़ंक्शन UPSERT का उपयोग करता है (INSERT ... ON CONFLICT .. DO UPDATE ) से INSERT या SELECT एक एकल पंक्ति :

CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int)
  LANGUAGE plpgsql AS
$func$
BEGIN
   SELECT tag_id  -- only if row existed before
   FROM   tag
   WHERE  tag = _tag
   INTO   _tag_id;

   IF NOT FOUND THEN
      INSERT INTO tag AS t (tag)
      VALUES (_tag)
      ON     CONFLICT (tag) DO NOTHING
      RETURNING t.tag_id
      INTO   _tag_id;
   END IF;
END
$func$;

दौड़ की स्थिति के लिए अभी भी एक छोटी सी खिड़की है। बिल्कुल सुनिश्चित . करने के लिए हमें एक आईडी मिलती है:

CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int)
  LANGUAGE plpgsql AS
$func$
BEGIN
   LOOP
      SELECT tag_id
      FROM   tag
      WHERE  tag = _tag
      INTO   _tag_id;

      EXIT WHEN FOUND;

      INSERT INTO tag AS t (tag)
      VALUES (_tag)
      ON     CONFLICT (tag) DO NOTHING
      RETURNING t.tag_id
      INTO   _tag_id;

      EXIT WHEN FOUND;
   END LOOP;
END
$func$;

db<>फिडल यहाँ

यह INSERT . तक लूपिंग करता रहता है या SELECT सफल हुआ। कॉल करें:

SELECT f_tag_id('possibly_new_tag');

यदि अनुवर्ती आदेश उसी लेन-देन में पंक्ति के अस्तित्व पर भरोसा करते हैं और यह वास्तव में संभव है कि अन्य लेन-देन अपडेट या इसे समवर्ती रूप से हटा दें, आप मौजूदा पंक्ति को SELECT में लॉक कर सकते हैं FOR SHARE . के साथ कथन .
यदि इसके बजाय पंक्ति सम्मिलित हो जाती है, तो लेन-देन के अंत तक इसे लॉक कर दिया जाता है (या अन्य लेनदेन के लिए दृश्यमान नहीं)।

सामान्य मामले से शुरू करें (INSERT बनाम SELECT ) इसे और तेज़ बनाने के लिए।

संबंधित:

  • सशर्त INSERT से आईडी प्राप्त करें
  • INSERT से RETURNING ... ON CONFLICT में बहिष्कृत पंक्तियों को कैसे शामिल करें

INSERT . से संबंधित (शुद्ध SQL) समाधान या SELECT एकाधिक पंक्तियां (एक सेट) एक बार में:

  • PostgreSQL में ON CONFLICT के साथ RETURNING का उपयोग कैसे करें?

इसमें क्या गलत है इसमें शुद्ध SQL समाधान?

CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int)
  LANGUAGE sql AS
$func$
WITH ins AS (
   INSERT INTO tag AS t (tag)
   VALUES (_tag)
   ON     CONFLICT (tag) DO NOTHING
   RETURNING t.tag_id
   )
SELECT tag_id FROM ins
UNION  ALL
SELECT tag_id FROM tag WHERE tag = _tag
LIMIT  1;
$func$;

पूरी तरह से गलत नहीं है, लेकिन यह एक खामियों को दूर करने में विफल रहता है, जैसे @FunctorSalad ने काम किया। फ़ंक्शन एक खाली परिणाम के साथ आ सकता है यदि एक समवर्ती लेनदेन एक ही समय में ऐसा करने का प्रयास करता है। मैनुअल:

<ब्लॉकक्वॉट>

सभी स्टेटमेंट एक ही स्नैपशॉट के साथ निष्पादित होते हैं

यदि कोई समवर्ती लेन-देन एक क्षण पहले वही नया टैग सम्मिलित करता है, लेकिन अभी तक प्रतिबद्ध नहीं है:

  • समवर्ती लेनदेन समाप्त होने की प्रतीक्षा करने के बाद, यूपीएसईआरटी भाग खाली हो जाता है। (यदि समवर्ती लेन-देन वापस होना चाहिए, तो यह अभी भी नया टैग सम्मिलित करता है और एक नई आईडी देता है।)

  • सेलेक्ट पार्ट भी खाली आता है, क्योंकि यह उसी स्नैपशॉट पर आधारित होता है, जहां (अभी तक अप्रतिबद्ध) समवर्ती लेनदेन का नया टैग दिखाई नहीं देता है।

हमें कुछ नहीं मिलता है . जैसा इरादा नहीं था। यह भोले तर्क के प्रति-सहज ज्ञान युक्त है (और मैं वहां पकड़ा गया), लेकिन पोस्टग्रेज का एमवीसीसी मॉडल इस तरह काम करता है - काम करना है।

इसलिए इसका उपयोग न करें यदि एक से अधिक लेन-देन एक ही समय में एक ही टैग डालने का प्रयास कर सकते हैं। या लूप जब तक आप वास्तव में एक पंक्ति प्राप्त नहीं करते। वैसे भी सामान्य कार्य भार में लूप को शायद ही कभी ट्रिगर किया जाएगा।

9.4 या पुराने को पोस्ट करें

इसे देखते हुए (थोड़ा सरलीकृत) तालिका:

CREATE table tag (
  tag_id serial PRIMARY KEY
, tag    text   UNIQUE
);

एक लगभग 100% सुरक्षित नया टैग डालने / मौजूदा एक का चयन करने के लिए कार्य, इस तरह दिख सकता है।

CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT tag_id int)
  LANGUAGE plpgsql AS
$func$
BEGIN
   LOOP
      BEGIN
      WITH sel AS (SELECT t.tag_id FROM tag t WHERE t.tag = _tag FOR SHARE)
         , ins AS (INSERT INTO tag(tag)
                   SELECT _tag
                   WHERE  NOT EXISTS (SELECT 1 FROM sel)  -- only if not found
                   RETURNING tag.tag_id)       -- qualified so no conflict with param
      SELECT sel.tag_id FROM sel
      UNION  ALL
      SELECT ins.tag_id FROM ins
      INTO   tag_id;

      EXCEPTION WHEN UNIQUE_VIOLATION THEN     -- insert in concurrent session?
         RAISE NOTICE 'It actually happened!'; -- hardly ever happens
      END;

      EXIT WHEN tag_id IS NOT NULL;            -- else keep looping
   END LOOP;
END
$func$;

db<>फिडल यहाँ
पुराना sqlfiddle

100% क्यों नहीं? संबंधित UPSERT . के लिए मैनुअल में नोटों पर विचार करें उदाहरण:

  • https://www.postgresql.org/docs/current/plpgsql-control-structs.html#PLPGSQL-UPSERT-EXAMPLE

स्पष्टीकरण

  • SELECTकोशिश करें पहले . इस तरह आप काफी अधिक महंगे . से बचते हैं 99.99% समय अपवाद हैंडलिंग।

  • दौड़ की स्थिति के लिए (पहले से ही छोटा) समय स्लॉट को कम करने के लिए सीटीई का उपयोग करें।

  • SELECT . के बीच समय विंडो और INSERT एक प्रश्न के भीतर अति सूक्ष्म है। यदि आपके पास भारी समवर्ती भार नहीं है, या यदि आप वर्ष में एक बार अपवाद के साथ रह सकते हैं, तो आप केवल मामले को अनदेखा कर सकते हैं और SQL कथन का उपयोग कर सकते हैं, जो तेज़ है।

  • FETCH FIRST ROW ONLY . की कोई आवश्यकता नहीं है (=LIMIT 1 ) टैग नाम स्पष्ट रूप से UNIQUE है ।

  • FOR SHARE निकालें मेरे उदाहरण में यदि आपके पास आमतौर पर समवर्ती नहीं है DELETE या UPDATE टेबल पर tag . थोड़ा सा प्रदर्शन खर्च होता है।

  • भाषा का नाम कभी भी उद्धृत न करें:<स्ट्राइक>'plpgsql' . plpgsql एक पहचानकर्ता है . उद्धरण समस्या पैदा कर सकता है और केवल पश्चगामी संगतता के लिए सहन किया जाता है।

  • id . जैसे गैर-वर्णनात्मक कॉलम नामों का उपयोग न करें या name . कुछ तालिकाओं में शामिल होने पर (आप यही करते हैं एक रिलेशनल डीबी में) आप कई समान नामों के साथ समाप्त होते हैं और उपनामों का उपयोग करना पड़ता है।

आपके फ़ंक्शन में अंतर्निहित

इस फ़ंक्शन का उपयोग करके आप अपने FOREACH LOOP . को काफी हद तक सरल बना सकते हैं करने के लिए:

...
FOREACH TagName IN ARRAY $3
LOOP
   INSERT INTO taggings (PostId, TagId)
   VALUES   (InsertedPostId, f_tag_id(TagName));
END LOOP;
...

तेज़, हालांकि, unnest() . के साथ एकल SQL कथन के रूप में :

INSERT INTO taggings (PostId, TagId)
SELECT InsertedPostId, f_tag_id(tag)
FROM   unnest($3) tag;

पूरे लूप को बदल देता है।

वैकल्पिक समाधान

यह संस्करण UNION ALL . के व्यवहार पर आधारित है LIMIT . के साथ खंड:जैसे ही पर्याप्त पंक्तियां मिलती हैं, बाकी को कभी भी निष्पादित नहीं किया जाता है:

  • परिणाम उपलब्ध होने तक एकाधिक चयनों को आज़माने का तरीका?

इस पर निर्माण करते हुए, हम INSERT . को आउटसोर्स कर सकते हैं एक अलग समारोह में। केवल वहां हमें अपवाद से निपटने की आवश्यकता है। पहले समाधान जितना ही सुरक्षित।

CREATE OR REPLACE FUNCTION f_insert_tag(_tag text, OUT tag_id int)
  RETURNS int
  LANGUAGE plpgsql AS
$func$
BEGIN
   INSERT INTO tag(tag) VALUES (_tag) RETURNING tag.tag_id INTO tag_id;

   EXCEPTION WHEN UNIQUE_VIOLATION THEN  -- catch exception, NULL is returned
END
$func$;

जिसका उपयोग मुख्य कार्य में किया जाता है:

CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int)
   LANGUAGE plpgsql AS
$func$
BEGIN
   LOOP
      SELECT tag_id FROM tag WHERE tag = _tag
      UNION  ALL
      SELECT f_insert_tag(_tag)  -- only executed if tag not found
      LIMIT  1  -- not strictly necessary, just to be clear
      INTO   _tag_id;

      EXIT WHEN _tag_id IS NOT NULL;  -- else keep looping
   END LOOP;
END
$func$;
  • यह थोड़ा सस्ता है यदि अधिकांश कॉलों को केवल SELECT की आवश्यकता होती है , क्योंकि INSERT . के साथ अधिक महंगा ब्लॉक जिसमें EXCEPTION . है खंड शायद ही कभी दर्ज किया जाता है। क्वेरी भी आसान है।

  • FOR SHARE यहां संभव नहीं है (UNION . में अनुमति नहीं है क्वेरी)।

  • LIMIT 1 आवश्यक नहीं होगा (पृष्ठ 9.4 में परीक्षण)। पोस्टग्रेज़ LIMIT 1 प्राप्त करता है INTO _tag_id . से और केवल पहली पंक्ति मिलने तक निष्पादित होता है।



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Postgresql में कैरिज रिटर्न और नई लाइनें कैसे निकालें?

  2. पोस्टग्रेज़ नई पंक्ति का संदर्भ देने वाली पंक्ति के लिए NULL मान फ़ंक्शन करता है

  3. n समूहीकृत श्रेणियां प्राप्त करें और दूसरों को एक में जोड़ें

  4. वॉल्यूम का उपयोग करके डॉक किए गए पोस्टग्रेज डेटाबेस में डेटा को कैसे बनाए रखें

  5. PL/pgSQL के अंदर INSERT के बाद डिफ़ॉल्ट सीरियल मान प्राप्त करें