12

मैं पाइथन (वास्तव में प्रासंगिक नहीं) और पोस्टग्रेस्क्ल (9.2 यदि प्रासंगिक हो) का उपयोग कर एक साधारण वेब आधारित आरएसएस रीडर को कार्यान्वित कर रहा हूं। डेटाबेस स्कीमा के रूप में निम्नानुसार (आरएसएस प्रारूप के आधार पर) है: जब मैं एक नया चैनल बनाने (और यह भी नवीनीकृत फ़ीड की जानकारी के लिए क्वेरी) मैं फ़ीड का अनुरोधयदि मौजूद नहीं है तो पंक्ति डालें रेस की स्थिति की ओर जाता है?

CREATE TABLE feed_channel 
(
    id SERIAL PRIMARY KEY, 
    name TEXT, 
    link TEXT NOT NULL, 
    title TEXT 
); 
CREATE TABLE feed_content 
(
    id SERIAL PRIMARY KEY, 
    channel INTEGER REFERENCES feed_channel(id) ON DELETE CASCADE ON UPDATE CASCADE, 
    guid TEXT UNIQUE NOT NULL, 
    title TEXT, 
    link TEXT, 
    description TEXT, 
    pubdate TIMESTAMP 
); 

, feed_channel मेज पर अपने डेटा डालें, चयन नई डाली गई आईडी - या डुप्लिकेट से बचने के लिए मौजूदा - और फिर फीड डेटा को feed_content तालिका में जोड़ें। एक ठेठ परिदृश्य होगा:

  1. क्वेरी फ़ीड url, हड़पने फ़ीड हेडर और सभी मौजूदा सामग्री
  2. सम्मिलित feed_channel में फ़ीड हेडर मौजूद नहीं करता है, तो ... पहले से ही मौजूद हैं, मौजूदा आईडी हड़पने
  3. प्रत्येक फीड आइटम के लिए, संग्रहित चैनल आईडी

के संदर्भ में feed_content तालिका में डालें यह मानक है "डालें यदि पहले से मौजूद नहीं है, लेकिन प्रासंगिक आईडी लौटाएं" समस्या है। इस मैं निम्नलिखित संग्रहीत प्रक्रिया को लागू किया है हल करने के लिए:

CREATE OR REPLACE FUNCTION channel_insert(
    p_link feed_channel.link%TYPE, 
    p_title feed_channel.title%TYPE 
) RETURNS feed_channel.id%TYPE AS $$ 
    DECLARE 
    v_id feed_channel.id%TYPE; 
    BEGIN 
    SELECT id 
    INTO v_id 
    FROM feed_channel 
    WHERE link=p_link AND title=p_title 
    LIMIT 1; 

    IF v_id IS NULL THEN 
     INSERT INTO feed_channel(name,link,title) 
     VALUES (DEFAULT,p_link,p_title) 
     RETURNING id INTO v_id; 
    END IF; 

    RETURN v_id; 

    END; 
$$ LANGUAGE plpgsql; 

यह तो कहा जाता है "channel_insert (लिंक, शीर्षक) का चयन करें," मेरे आवेदन से सम्मिलित करने के लिए यदि पहले से मौजूद नहीं है और फिर प्रासंगिक पंक्ति की आईडी वापस लौटाए चाहे वह डाला गया हो या अभी पाया गया हो (ऊपर सूची में चरण 2)।

यह बहुत अच्छा काम करता है!

हालांकि, मैंने हाल ही में यह सोचना शुरू कर दिया कि क्या होगा यदि इस प्रक्रिया को एक ही समय में एक ही तर्क के साथ दो बार निष्पादित किया गया था। मान देता है निम्नलिखित:

  1. उपयोगकर्ता एक नया चैनल जोड़ने के लिए 1 के प्रयास और इस तरह अमल channel_insert
  2. कुछ एमएस बाद में, उपयोगकर्ता 2 प्रयत्न ही चैनल जोड़ने के लिए है और यह भी channel_insert
  3. के लिए उपयोगकर्ता 1 के चेक पर अमल मौजूदा पंक्तियां पूरी होती हैं, लेकिन सम्मिलन पूर्ण होने से पहले, उपयोगकर्ता 2 की जांच पूरी होती है और कहती है कि कोई मौजूदा पंक्तियां नहीं हैं।

क्या यह पोस्टग्रेएसक्यूएल में संभावित दौड़ की स्थिति होगी? ऐसे परिदृश्यों से बचने के लिए इस समस्या को हल करने का सबसे अच्छा तरीका क्या है? क्या पूरे संग्रहित प्रक्रिया को परमाणु रूप से बनाना संभव है, यानी कि इसे एक ही समय में केवल एक बार निष्पादित किया जा सकता है?

एक विकल्प मैंने कोशिश की थी कि वह फ़ील्ड अद्वितीय बनाएं और फिर पहले डालने का प्रयास करें, और यदि अपवाद है, तो इसके बजाय मौजूदा का चयन करें ... यह काम करता है, हालांकि, प्रत्येक प्रयास के लिए सीरियल फील्ड बढ़ेगा, जिससे बहुत कुछ अनुक्रम में अंतराल। मुझे नहीं पता कि यह लंबे समय तक (शायद नहीं) में एक समस्या होगी, लेकिन परेशान करने की तरह। शायद यह पसंदीदा समाधान है?

किसी भी प्रतिक्रिया के लिए धन्यवाद। PostgreSQL जादू का यह स्तर मेरे बाहर है, इसलिए किसी भी प्रतिक्रिया की सराहना की जाएगी।

+1

.com'), पैरामीटर ऑर्डर जारी ('? ए = बी और सी = डी' और'? सी = डी और ए = बी'), आदि –

+0

डुप्लिकेट कुंजी उल्लंघन के मामले में एक plpgsql फ़ंक्शन लूपिंग दौड़ की स्थिति से निपट सकती है सर्वर-साइड और डिफॉल्ट अलगाव स्तर पर, जो * सुरक्षित * एक आम तौर पर * सबसे सस्ता * है: http://stackoverflow.com/questions/15939902/is-select-or-insert-in-a-function-prone-to- दौड़-स्थितियां/15 9 50324 # 15 9 50324 –

उत्तर

4

यहां एक अपरिहार्य "दौड़" है, क्योंकि दो सत्र एक दूसरे को अनदेखी पंक्तियों को "देख" नहीं सकते हैं। एक संघर्ष पर, एक सत्र केवल रोलबैक (शायद एक सेवपॉइंट पर) और पुनः प्रयास कर सकता था। इसका आमतौर पर मतलब होगा: एक निजी डुप्लिकेट बनाने के बजाय, दूसरी ताजा डाली गई पंक्ति का जिक्र करना।

यहां डेटा-मॉडलिंग समस्या है: feed_channel में बहुत से उम्मीदवार कुंजी हैं, और feed_content से कैस्केडिंग नियम बहुत सारी फ़ीड_केंटेंट पंक्तियों को अनाथ कर सकता है (मुझे लगता है कि सामग्री-> चैनल 1 :: एम संबंध है; एक से अधिक सामग्रियों-पंक्ति एक ही चैनल को संदर्भित कर सकते हैं)

अंत में, कम से कम को feed_channel तालिका प्राकृतिक कुंजी {लिंक, शीर्षक} की आवश्यकता है। वह जगह है जहां सम्मिलित/मौजूद नहीं है। (और इस फ़ंक्शन का पूरा उद्देश्य)

मैंने फ़ंक्शन को थोड़ा सा साफ़ कर दिया। IF निर्माण की आवश्यकता नहीं है, INSERT जहांपहले ठीक से काम करता है, और शायद बेहतर भी काम करता है।

DROP SCHEMA tmp CASCADE; 
CREATE SCHEMA tmp ; 
SET search_path=tmp; 

CREATE TABLE feed_channel 
    (id SERIAL PRIMARY KEY 
    , name TEXT 
    , link TEXT NOT NULL 
    , title TEXT NOT NULL -- part of PK :: must be not nullable 
    , CONSTRAINT feed_channel_nat UNIQUE (link,title) -- the natural key 
); 

CREATE TABLE feed_content 
    (id SERIAL PRIMARY KEY 
    , channel INTEGER REFERENCES feed_channel(id) ON DELETE CASCADE ON UPDATE CASCADE 
    , guid TEXT UNIQUE NOT NULL -- yet another primary key 
    , title TEXT -- 
    , link TEXT -- title && link appear to be yet another candidate key 
    , description TEXT 
    , pubdate TIMESTAMP 
    ); 

-- NOTE: omitted original function channel_insert() for brevity 
CREATE OR REPLACE FUNCTION channel_insert_wp(
    p_link feed_channel.link%TYPE, 
    p_title feed_channel.title%TYPE 
) RETURNS feed_channel.id%TYPE AS $body$ 
    DECLARE 
    v_id feed_channel.id%TYPE; 
    BEGIN 
     INSERT INTO feed_channel(link,title) 
     SELECT p_link,p_title 
     WHERE NOT EXISTS (SELECT * 
     FROM feed_channel nx 
     WHERE nx.link= p_link 
     AND nx.title= p_title 
     ) 
     ; 
    SELECT id INTO v_id 
    FROM feed_channel ex 
    WHERE ex.link= p_link 
    AND ex.title= p_title 
     ; 

    RETURN v_id; 

    END; 
$body$ LANGUAGE plpgsql; 

SELECT channel_insert('Bogus_link', 'Bogus_title'); 
SELECT channel_insert_wp('Bogus_link2', 'Bogus_title2'); 

SELECT * FROM feed_channel; 

परिणाम:

DROP SCHEMA 
CREATE SCHEMA 
SET 
NOTICE: CREATE TABLE will create implicit sequence "feed_channel_id_seq" for serial column "feed_channel.id" 
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index "feed_channel_pkey" for table "feed_channel" 
NOTICE: CREATE TABLE/UNIQUE will create implicit index "feed_channel_nat" for table "feed_channel" 
CREATE TABLE 
NOTICE: CREATE TABLE will create implicit sequence "feed_content_id_seq" for serial column "feed_content.id" 
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index "feed_content_pkey" for table "feed_content" 
NOTICE: CREATE TABLE/UNIQUE will create implicit index "feed_content_guid_key" for table "feed_content" 
CREATE TABLE 
NOTICE: type reference feed_channel.link%TYPE converted to text 
NOTICE: type reference feed_channel.title%TYPE converted to text 
NOTICE: type reference feed_channel.id%TYPE converted to integer 
CREATE FUNCTION 
NOTICE: type reference feed_channel.link%TYPE converted to text 
NOTICE: type reference feed_channel.title%TYPE converted to text 
NOTICE: type reference feed_channel.id%TYPE converted to integer 
CREATE FUNCTION 
channel_insert 
---------------- 
       1 
(1 row) 

channel_insert_wp 
------------------- 
       2 
(1 row) 

id | name | link  | title  
----+------+-------------+-------------- 
    1 |  | Bogus_link | Bogus_title 
    2 |  | Bogus_link2 | Bogus_title2 
(2 rows) 
कोई फर्क नहीं पड़ता है, ताकि आप मामले समस्याओं (`नहीं है Www.Example.Com` और` www.example क्या आप करते हैं, अपने लिंक प्रारूप को सामान्य बनाने में सावधान रहना
+1

मैं संग्रहीत प्रक्रिया को पूरी तरह से और बदले में ड्रॉप, तो बस का उपयोग करें: 'जांच feed_channel (स्रोत) INSERT % (स्रोत) रों जहां मौजूद नहीं है का चयन करें ( feed_channel से चयन 1 जहां स्रोत =% (स्रोत) रों );' क्या मैं कभी भी एक अद्वितीय उल्लंघन मानता हूं कि "स्रोत" में एक अनूठी बाधा है? – agnsaft

3

आपकी सबसे बड़ी समस्या यह है कि serialfeed_channel तालिका के लिए एक अच्छी प्राथमिक कुंजी नहीं बनाता है। प्राथमिक कुंजी (link, title) या (link) होना चाहिए यदि titlenull हो सकता है। फिर कोई मौजूदा फ़ीड डालने का प्रयास प्राथमिक कुंजी त्रुटि को बढ़ाएगा।

BTW v_idnull हो जाएगा जब भी titlenull है: PostgreSQL में एक संभावित रेस स्थिति

WHERE link=p_link AND title=p_title 
+0

वह है एक दिलचस्प विचार है, लेकिन यदि प्राथमिक कुंजी में कई फ़ील्ड होते हैं तो संदर्भ/विदेशी कुंजी बनाने में दर्द नहीं होगा? इसके अलावा, मैंने पहली बार एक ही परिणाम प्राप्त करने के लिए एक अनूठी बाधा बनाने की कोशिश की, हालांकि, मुझे उस अद्वितीय दृष्टिकोण के कारण डालने की विफलता पर भी सही आईडी वापस करने की आवश्यकता है क्योंकि मैंने उस दृष्टिकोण को त्याग दिया था। इसके अलावा, मुझे लगता है कि अचानक तनाव से निपटने में परेशानी होती है जब अचानक बाधाओं के कारण विफलता होती है। – agnsaft

+1

@invictus प्राथमिक कुंजी में कई फ़ील्ड शामिल होने पर संदर्भ/विदेशी कुंजी बनाने का दर्द क्यों होगा? कोई आईडी नहीं होगा। आपको कुछ भी वापस नहीं करना पड़ेगा क्योंकि आप पहले ही जानते थे कि आपने किस प्राकृतिक कुंजी (लिंक, शीर्षक) को सम्मिलित करने का प्रयास किया था। –

4

इस होगा?

हां, और वास्तव में यह किसी भी डेटाबेस इंजन में होगा।

ऐसी परिस्थितियों से बचने के लिए इस समस्या को हल करने का सबसे अच्छा तरीका क्या है?

यह एक भारित प्रश्न है और एकाधिक उपयोगकर्ताओं द्वारा डेटाबेस के उपयोग के अंतरंग ज्ञान की आवश्यकता होगी। हालांकि, मैं आपको कुछ विकल्प देने जा रहा हूं। संक्षेप में, एक ही विकल्प आपके पास इस प्रक्रिया के दौरान मेज LOCK लिए है, लेकिन कैसे आप लॉक कि तालिका पर निर्भर करेगा कैसे डेटाबेस दिन भर में प्रयोग किया जाता है।

की बुनियादी LOCK साथ शुरू करते हैं:

LOCK TABLE feed_channel 

कि ACCESS EXCLUSIVE ताला विकल्प का उपयोग कर तालिका लॉक हो जाएगा। सभी साधनों के ताले (पहुँच साझा, पंक्ति शेयर, पंक्ति विशेष, अद्यतन साझा करें EXCLUSIVE, शेयर, शेयर पंक्ति, विशेष विशेष, और पहुंच EXCLUSIVE) के साथ

संघर्ष। यह मोड गारंटी देता है कि धारक किसी भी तरह से तालिका तक पहुंचने वाला एकमात्र लेनदेन है।

अब, यह सबसे प्रतिबंधात्मक ताला उपलब्ध है, और निश्चित रूप से रेस स्थिति को हल करेंगे, लेकिन वास्तव में आप क्या चाहते हैं नहीं हो सकता। ऐसा कुछ है जिसे आप तय करना चाहते हैं। तो, हालांकि यह स्पष्ट आप LOCK करने के लिए तालिका करने जा रहे हैं है, यह स्पष्ट नहीं है कैसे।

निर्णय लेने के लिए आप क्या छोड़ रहे हैं?

  1. कैसे आप LOCK the table करना चाहते हैं? अपना दृढ़ संकल्प करने के लिए उस लिंक पर लॉकिंग विकल्पों का अध्ययन करें।
  2. कहां क्या आप तालिका LOCK तालिका चाहते हैं? या दूसरे शब्दों में, आप (संभव रेस स्थिति के आधार पर करते हैं जो मैं आपको लगता है) LOCKशीर्ष समारोह के पर करना चाहते हैं, या आप बस सही INSERT से पहले LOCK करना चाहते हैं?

यह पूरे संग्रहीत प्रक्रिया atomically है, यानी कि यह केवल एक ही समय में एक बार क्रियान्वित किया जा सकता बनाने के लिए संभव है?

नहीं, कोड डेटाबेस से कनेक्ट किसी भी व्यक्ति द्वारा निष्पादित किया जा सकता है।


मुझे आशा है कि इससे आपको निर्देशित करने में मदद मिलेगी।

संबंधित मुद्दे