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

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

वर्तमान में स्वीकृत उत्तर एकल संघर्ष लक्ष्य, कुछ संघर्षों, छोटे टुपल्स और बिना ट्रिगर के ठीक लगता है। यह समवर्ती समस्या 1 . से बचा जाता है (नीचे देखें) पाशविक बल के साथ। सरल समाधान की अपनी अपील है, दुष्प्रभाव कम महत्वपूर्ण हो सकते हैं।

हालांकि, अन्य सभी मामलों के लिए, नहीं करें आवश्यकता के बिना समान पंक्तियों को अद्यतन करें। यहां तक ​​कि अगर आप सतह पर कोई अंतर नहीं देखते हैं, तो भी विभिन्न दुष्प्रभाव हैं :

  • यह ट्रिगर्स को सक्रिय कर सकता है जिन्हें सक्रिय नहीं किया जाना चाहिए।

  • यह "मासूम" पंक्तियों को राइट-लॉक करता है, संभवतः समवर्ती लेनदेन के लिए लागत वहन करती है।

  • यह पंक्ति को नई लग सकती है, हालांकि यह पुरानी है (लेन-देन टाइमस्टैम्प)।

  • सबसे महत्वपूर्ण , PostgreSQL के MVCC मॉडल के साथ प्रत्येक UPDATE . के लिए एक नया पंक्ति संस्करण लिखा जाता है , कोई फर्क नहीं पड़ता कि क्या पंक्ति डेटा बदल गया है। यह UPSERT के लिए एक प्रदर्शन दंड, टेबल ब्लोट, इंडेक्स ब्लोट, टेबल पर बाद के संचालन के लिए प्रदर्शन जुर्माना, VACUUM लगता है कीमत। कुछ डुप्लीकेट के लिए मामूली प्रभाव, लेकिन बड़े पैमाने पर ज्यादातर ठगों के लिए।

प्लस , कभी-कभी ON CONFLICT DO UPDATE . का उपयोग करना व्यावहारिक या संभव नहीं होता है . मैनुअल:

<ब्लॉककोट>

ON CONFLICT DO UPDATE , एक conflict_target प्रदान किया जाना चाहिए।

एक एकल यदि एकाधिक अनुक्रमणिका/बाधाएं शामिल हैं तो "संघर्ष लक्ष्य" संभव नहीं है। लेकिन यहां एकाधिक आंशिक अनुक्रमणिका के लिए संबंधित समाधान दिया गया है:

  • यूपीएसईआरटी NULL मानों के साथ UNIQUE बाधा पर आधारित

विषय पर वापस, आप खाली अपडेट और साइड इफेक्ट के बिना (लगभग) समान प्राप्त कर सकते हैं। निम्नलिखित में से कुछ समाधान ON CONFLICT DO NOTHING के साथ भी काम करते हैं (कोई "संघर्ष लक्ष्य नहीं"), सभी को पकड़ने के लिए संभावित संघर्ष जो उत्पन्न हो सकते हैं - जो वांछनीय हो भी सकते हैं और नहीं भी।

समवर्ती लेखन भार के बिना

WITH input_rows(usr, contact, name) AS (
   VALUES
      (text 'foo1', text 'bar1', text 'bob1')  -- type casts in first row
    , ('foo2', 'bar2', 'bob2')
    -- more?
   )
, ins AS (
   INSERT INTO chats (usr, contact, name) 
   SELECT * FROM input_rows
   ON CONFLICT (usr, contact) DO NOTHING
   RETURNING id  --, usr, contact              -- return more columns?
   )
SELECT 'i' AS source                           -- 'i' for 'inserted'
     , id  --, usr, contact                    -- return more columns?
FROM   ins
UNION  ALL
SELECT 's' AS source                           -- 's' for 'selected'
     , c.id  --, usr, contact                  -- return more columns?
FROM   input_rows
JOIN   chats c USING (usr, contact);           -- columns of unique index

source कॉलम यह दिखाने के लिए एक वैकल्पिक जोड़ है कि यह कैसे काम करता है। दोनों मामलों के बीच अंतर बताने के लिए आपको वास्तव में इसकी आवश्यकता हो सकती है (खाली लिखने पर एक और फायदा)।

अंतिम JOIN chats काम करता है क्योंकि संलग्न डेटा-संशोधित सीटीई से नई सम्मिलित पंक्तियां अभी तक अंतर्निहित तालिका में दिखाई नहीं दे रही हैं। (एक ही SQL कथन के सभी भाग अंतर्निहित तालिकाओं के समान स्नैपशॉट देखते हैं।)

चूंकि VALUES अभिव्यक्ति स्वतंत्र है (सीधे INSERT . से जुड़ी नहीं है ) पोस्टग्रेज लक्ष्य कॉलम से डेटा प्रकार प्राप्त नहीं कर सकते हैं और आपको स्पष्ट प्रकार के कास्ट जोड़ने पड़ सकते हैं। मैनुअल:

<ब्लॉककोट>

जब VALUES INSERT . में प्रयोग किया जाता है , मान सभी स्वचालित रूप से संबंधित गंतव्य कॉलम के डेटा प्रकार के लिए बाध्य होते हैं। जब इसका उपयोग अन्य संदर्भों में किया जाता है, तो सही डेटा प्रकार निर्दिष्ट करना आवश्यक हो सकता है। यदि प्रविष्टियाँ सभी उद्धृत शाब्दिक स्थिरांक हैं, तो पहले को ज़बरदस्ती करना सभी के लिए कल्पित प्रकार को निर्धारित करने के लिए पर्याप्त है।

कुछ . के लिए स्वयं क्वेरी (दुष्प्रभावों की गिनती नहीं) थोड़ी अधिक महंगी हो सकती है ठगी, सीटीई के ऊपरी हिस्से और अतिरिक्त SELECT . के कारण (जो सस्ता होना चाहिए क्योंकि परिभाषा के अनुसार सही सूचकांक है - एक सूचकांक के साथ एक अद्वितीय बाधा लागू की जाती है)।

कई . के लिए (बहुत) तेज हो सकता है डुप्लीकेट। अतिरिक्त लेखन की प्रभावी लागत कई कारकों पर निर्भर करती है।

लेकिन इसके कम दुष्प्रभाव और छिपी हुई लागतें हैं किसी भी स्थिति में। यह संभवत:समग्र रूप से सबसे सस्ता है।

संलग्न अनुक्रम अभी भी उन्नत हैं, क्योंकि डिफ़ॉल्ट मान पहले . में भरे गए हैं विरोधों के लिए परीक्षण।

सीटीई के बारे में:

  • क्या केवल SELECT टाइप क्वेश्चन ही नेस्ट किया जा सकता है?
  • रिलेशनल डिवीज़न में SELECT स्टेटमेंट को डुप्लीकेट करें

समवर्ती लेखन लोड के साथ

डिफ़ॉल्ट मान लें READ COMMITTED लेनदेन अलगाव। संबंधित:

  • समवर्ती लेन-देन का परिणाम दौड़ की स्थिति में होता है जिसमें सम्मिलित करने पर अद्वितीय बाधा होती है

दौड़ की स्थितियों से बचाव के लिए सबसे अच्छी रणनीति सटीक आवश्यकताओं, तालिका और यूपीएसईआरटी में पंक्तियों की संख्या और आकार, समवर्ती लेनदेन की संख्या, संघर्षों की संभावना, उपलब्ध संसाधनों और अन्य कारकों पर निर्भर करती है ...

Concurrency समस्या 1

यदि एक समवर्ती लेन-देन एक पंक्ति में लिखा गया है जिसे आपका लेन-देन अब UPSERT को करने का प्रयास करता है, तो आपके लेन-देन को दूसरे के समाप्त होने की प्रतीक्षा करनी होगी।

यदि अन्य लेन-देन ROLLBACK के साथ समाप्त होता है (या कोई त्रुटि, यानी स्वचालित ROLLBACK ), आपका लेन-देन सामान्य रूप से आगे बढ़ सकता है। मामूली संभावित दुष्प्रभाव:अनुक्रमिक संख्या में अंतराल। लेकिन कोई लापता पंक्तियाँ नहीं हैं।

यदि अन्य लेन-देन सामान्य रूप से समाप्त होता है (अंतर्निहित या स्पष्ट COMMIT ), आपका INSERT एक विरोध का पता लगाएगा (UNIQUE अनुक्रमणिका / बाधा पूर्ण है) और DO NOTHING , इसलिए भी पंक्ति वापस न करें। (जैसा कि संगामिति अंक 2 . में दिखाया गया है, पंक्ति को भी लॉक नहीं किया जा सकता है नीचे, क्योंकि यह दिखाई नहीं दे रहा है ।) SELECT क्वेरी की शुरुआत से एक ही स्नैपशॉट देखता है और अभी तक अदृश्य पंक्ति को वापस नहीं कर सकता है।

परिणाम सेट से ऐसी कोई भी पंक्ति गायब है (भले ही वे अंतर्निहित तालिका में मौजूद हों)!

यह जैसा है ठीक हो सकता है . खासकर यदि आप उदाहरण की तरह पंक्तियों को वापस नहीं कर रहे हैं और यह जानकर संतुष्ट हैं कि पंक्ति है। यदि यह पर्याप्त नहीं है, तो इसके कई तरीके हैं।

यदि आप इनपुट की पंक्ति गणना से मेल नहीं खाते हैं तो आप आउटपुट की पंक्ति गणना की जांच कर सकते हैं और कथन दोहरा सकते हैं। दुर्लभ मामले के लिए काफी अच्छा हो सकता है। मुद्दा एक नई क्वेरी शुरू करना है (उसी लेनदेन में हो सकता है), जो तब नई प्रतिबद्ध पंक्तियों को देखेगा।

या अंदर . लापता परिणाम पंक्तियों के लिए जाँच करें वही क्वेरी और ओवरराइट अलेक्स्टोनी के जवाब में ब्रूट फ़ोर्स ट्रिक का प्रदर्शन किया।

WITH input_rows(usr, contact, name) AS ( ... )  -- see above
, ins AS (
   INSERT INTO chats AS c (usr, contact, name) 
   SELECT * FROM input_rows
   ON     CONFLICT (usr, contact) DO NOTHING
   RETURNING id, usr, contact                   -- we need unique columns for later join
   )
, sel AS (
   SELECT 'i'::"char" AS source                 -- 'i' for 'inserted'
        , id, usr, contact
   FROM   ins
   UNION  ALL
   SELECT 's'::"char" AS source                 -- 's' for 'selected'
        , c.id, usr, contact
   FROM   input_rows
   JOIN   chats c USING (usr, contact)
   )
, ups AS (                                      -- RARE corner case
   INSERT INTO chats AS c (usr, contact, name)  -- another UPSERT, not just UPDATE
   SELECT i.*
   FROM   input_rows i
   LEFT   JOIN sel   s USING (usr, contact)     -- columns of unique index
   WHERE  s.usr IS NULL                         -- missing!
   ON     CONFLICT (usr, contact) DO UPDATE     -- we've asked nicely the 1st time ...
   SET    name = c.name                         -- ... this time we overwrite with old value
   -- SET name = EXCLUDED.name                  -- alternatively overwrite with *new* value
   RETURNING 'u'::"char" AS source              -- 'u' for updated
           , id  --, usr, contact               -- return more columns?
   )
SELECT source, id FROM sel
UNION  ALL
TABLE  ups;

यह ऊपर की क्वेरी की तरह है, लेकिन हम CTE ups . के साथ एक और चरण जोड़ते हैं , इससे पहले कि हम पूर्ण . लौटाएं परिणाम सेट। वह आखिरी सीटीई ज्यादातर समय कुछ नहीं करेगा। केवल अगर लौटे परिणाम से पंक्तियाँ गायब हो जाती हैं, तो हम पाशविक बल का उपयोग करते हैं।

अधिक ओवरहेड, अभी तक। पूर्व-मौजूदा पंक्तियों के साथ जितना अधिक विरोध होगा, उतनी ही अधिक संभावना है कि यह सरल दृष्टिकोण से बेहतर प्रदर्शन करेगा।

एक साइड इफेक्ट:दूसरा यूपीएसईआरटी पंक्तियों को क्रम से लिखता है, इसलिए यह गतिरोध की संभावना को फिर से पेश करता है (नीचे देखें) यदि तीन या अधिक समान पंक्तियों में लिखने वाले लेन-देन ओवरलैप होते हैं। यदि यह एक समस्या है, तो आपको एक अलग समाधान की आवश्यकता है - जैसे ऊपर बताए अनुसार पूरे कथन को दोहराना।

Concurrency समस्या 2

यदि समवर्ती लेन-देन प्रभावित पंक्तियों के शामिल स्तंभों को लिख सकते हैं, और आपको यह सुनिश्चित करना होगा कि आपको मिली पंक्तियाँ उसी लेन-देन में बाद के चरण में अभी भी हैं, तो आप मौजूदा पंक्तियों को लॉक कर सकते हैं CTE में सस्ते में ins (जो अन्यथा अनलॉक हो जाएगा) इसके साथ:

...
ON CONFLICT (usr, contact) DO UPDATE
SET name = name WHERE FALSE  -- never executed, but still locks the row
...

और SELECT . में एक लॉकिंग क्लॉज जोड़ें साथ ही, जैसे FOR UPDATE

यह प्रतिस्पर्धी लेखन संचालन को लेन-देन के अंत तक प्रतीक्षा करता है, जब सभी ताले जारी किए जाते हैं। इसलिए संक्षिप्त रहें।

अधिक विवरण और स्पष्टीकरण:

  • INSERT से RETURNING ... ON CONFLICT में बहिष्कृत पंक्तियों को कैसे शामिल करें
  • क्या किसी ऐसे फ़ंक्शन में SELECT या INSERT है जो दौड़ की स्थिति से ग्रस्त है?

गतिरोध?

गतिरोधों से बचाव करें सुसंगत क्रम . में पंक्तियों को सम्मिलित करके . देखें:

  • संघर्ष के बावजूद कुछ भी नहीं करने के बावजूद बहु-पंक्ति INSERTs के साथ गतिरोध

डेटा प्रकार और कास्ट

डेटा प्रकारों के लिए टेम्पलेट के रूप में मौजूदा तालिका ...

फ्री-स्टैंडिंग VALUES . में डेटा की पहली पंक्ति के लिए स्पष्ट प्रकार कास्ट करता है अभिव्यक्ति असुविधाजनक हो सकती है। इसके चारों ओर रास्ते हैं। आप किसी भी मौजूदा संबंध (तालिका, दृश्य, ...) का उपयोग पंक्ति टेम्पलेट के रूप में कर सकते हैं। लक्ष्य तालिका उपयोग के मामले के लिए स्पष्ट विकल्प है। इनपुट डेटा को स्वचालित रूप से उपयुक्त प्रकारों के लिए बाध्य किया जाता है, जैसे VALUES . में एक INSERT . का खंड :

WITH input_rows AS (
  (SELECT usr, contact, name FROM chats LIMIT 0)  -- only copies column names and types
   UNION ALL
   VALUES
      ('foo1', 'bar1', 'bob1')  -- no type casts here
    , ('foo2', 'bar2', 'bob2')
   )
   ...

यह कुछ डेटा प्रकारों के लिए काम नहीं करता है। देखें:

  • कई पंक्तियों को अपडेट करते समय NULL प्रकार कास्ट करना

... और नाम

यह सभी के लिए भी काम करता है डेटा प्रकार।

तालिका के सभी (अग्रणी) कॉलम में सम्मिलित करते समय, आप कॉलम नामों को छोड़ सकते हैं। मान लें तालिका chats उदाहरण में केवल यूपीएसईआरटी में उपयोग किए गए 3 कॉलम शामिल हैं:

WITH input_rows AS (
   SELECT * FROM (
      VALUES
      ((NULL::chats).*)         -- copies whole row definition
      ('foo1', 'bar1', 'bob1')  -- no type casts needed
    , ('foo2', 'bar2', 'bob2')
      ) sub
   OFFSET 1
   )
   ...

इसके अलावा:"user" . जैसे आरक्षित शब्दों का प्रयोग न करें पहचानकर्ता के रूप में। वह एक भरी हुई फुटगन है। कानूनी, लोअर-केस, गैर-उद्धृत पहचानकर्ताओं का उपयोग करें। मैंने इसे usr . से बदल दिया ।



  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. psycopg का उपयोग करके डालने के लिए समस्या

  3. PostgreSQL CASE ... END कई शर्तों के साथ

  4. Postgres बाधा जोड़ते समय उपयोगकर्ता पर या उसके निकट सिंटैक्स त्रुटि

  5. पीजी रत्न स्थापित करना; त्रुटि:मणि देशी एक्सटेंशन बनाने में विफल