जब एक संग्रहीत कार्यविधि से 3 संग्रहीत कार्यविधियाँ प्रारंभ की जाती हैं तो रोलबैक कैसे करें


23

मेरे पास एक संग्रहीत प्रक्रिया है जो केवल उनके अंदर 3 संग्रहीत प्रक्रियाओं को निष्पादित करती है। मैं केवल 1 पैरामीटर का उपयोग कर रहा हूँ स्टोर करने के लिए अगर मास्टर एसपी सफल है।

यदि पहली संग्रहीत प्रक्रिया मास्टर संग्रहीत कार्यविधि में ठीक काम करती है, लेकिन दूसरी संग्रहीत प्रक्रिया विफल हो जाती है, तो क्या यह स्वतः ही SP के सभी SP में वापस आ जाएगी या क्या मुझे कुछ कमांड बनाना है?

यहाँ मेरी प्रक्रिया है:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO

अगर spNewBilling3एक त्रुटि फेंकता है, लेकिन आप रोल वापस नहीं करना चाहता spNewBilling2या spNewBilling1, तो बस हटाने [begin|rollback|commit] transaction createSavebillinginvoiceसे spSavesomename
माइक

जवाबों:


56

केवल सवाल में दिखाए गए कोड को देखते हुए, और तीन उप-procs कि कोई भी यह सोचते हैं किसी भी स्पष्ट लेन-देन से निपटने, तो हाँ, तीन उप-procs में से किसी में एक त्रुटि पकड़ा हो जाएगा किया है और ROLLBACKमें CATCHब्लॉक वापस सब शुरू करेगी काम की।

लेकिन यहाँ लेन-देन के बारे में ध्यान देने योग्य बातें हैं (कम से कम SQL सर्वर में):

  • केवल एक वास्तविक लेनदेन (पहले वाला) है, चाहे आप कितनी भी बार कॉल करेंBEGIN TRAN

    • आप किसी लेन-देन नाम कर सकते हैं (जैसा कि आप यहाँ किया है) और उस नाम लॉग में दिखाई देगा, लेकिन केवल पहले / बाहरी-सबसे लेन-देन के लिए अर्थ है नामकरण (क्योंकि फिर, पहले एक है लेन-देन)।
    • हर बार जब भी आपको फोन किया जाता है BEGIN TRAN, तो उसका नाम लिया जाता है या नहीं, लेन-देन काउंटर 1 से बढ़ा हुआ होता है।
    • आप वर्तमान स्तर को करके देख सकते हैं SELECT @@TRANCOUNT;
    • COMMITजब कोई आदेश @@TRANCOUNT2 या इसके बाद के संस्करण में जारी किया जाता है तो कम से अधिक कुछ भी नहीं होता है, एक समय में, लेनदेन काउंटर।
    • कुछ भी कभी जब तक एक के लिए प्रतिबद्ध है COMMITजब जारी किया जाता है @@TRANCOUNTपर है1
    • बस मामले में उपरोक्त जानकारी स्पष्ट रूप से इंगित नहीं करती है: लेनदेन के स्तर की परवाह किए बिना, लेनदेन का कोई वास्तविक घोंसला नहीं है।
  • सहेजें अंक के भीतर काम का एक सबसेट बनाने के लिए अनुमति देने के लेन-देन है कि पूर्ववत हो सकता है।

    • SAVE TRAN {save_point_name}कमांड के माध्यम से सेव पॉइंट बनाए / चिह्नित किए जाते हैं
    • अंक बचाएं कार्य के सबसेट की शुरुआत को चिह्नित करते हैं जो पूरे लेनदेन को वापस किए बिना पूर्ववत किया जा सकता है।
    • सहेजें बिंदु नामों को अद्वितीय होने की आवश्यकता नहीं है, लेकिन एक ही नाम का एक से अधिक बार उपयोग करना अभी भी अलग-अलग सहेजने वाले बिंदु बनाता है।
    • सेव पॉइंट्स को नेस्टेड किया जा सकता है।
    • सहेजें अंक प्रतिबद्ध नहीं किया जा सकता है।
    • सहेजें बिंदुओं को पूर्ववत किया जा सकता है ROLLBACK {save_point_name}। (नीचे इस पर और अधिक)
    • एक सेविंग बैक को रोल करना, किसी भी काम को पूर्ववत कर देगा, जो कि हाल ही में किए गए कॉल के बाद हुआ है SAVE TRAN {save_point_name}, जिसमें रोल-बैक बनने के बाद बनाए गए किसी भी सेव पॉइंट को शामिल किया गया है (इसलिए "नेस्टिंग")।
    • एक सेविंग बैक को रोल करने से ट्रांजैक्शन काउंट / लेवल पर कोई असर नहीं पड़ता है
    • प्रारंभिक से पहले किया गया कोई भी काम पूरे लेनदेन को SAVE TRANजारी करने के अलावा पूर्ववत नहीं किया जा सकता है ROLLBACK
    • बस स्पष्ट होने के लिए: COMMITजब एक जारी करना @@TRANCOUNT2 या उससे ऊपर है, तो बचत बिंदुओं पर कोई प्रभाव नहीं पड़ता है (क्योंकि फिर से, 1 से ऊपर लेनदेन का स्तर उस काउंटर के बाहर मौजूद नहीं है)।
  • आप विशिष्ट नामित लेनदेन नहीं कर सकते। लेन-देन "नाम", यदि प्रदान किया गया है COMMIT, को नजरअंदाज कर दिया गया है और केवल पठनीयता के लिए मौजूद है।

  • एक ROLLBACKनाम के बिना जारी किया गया हमेशा सभी लेनदेन को रोलबैक करेगा।

  • एक ROLLBACKनाम के साथ जारी किया जाना चाहिए:

    • पहला लेन-देन, यह
      मानते हुए नाम दिया गया था: मान SAVE TRANलिया गया है कि एक ही लेन-देन के नाम के साथ नहीं बुलाया गया है, यह सभी लेनदेन को रोलबैक करेगा।
    • एक "सेव पॉइंट" (ऊपर वर्णित):
      यह व्यवहार "पूर्ववत" होगा, जिसे सबसे हाल ही SAVE TRAN {save_point_name} में बुलाया गया था।
    • यदि पहला लेन-देन a) था, जिसका नाम और b) में SAVE TRANउसके नाम के साथ आदेश जारी किया गया है, तो उस लेन-देन के नाम का प्रत्येक रोल प्रत्येक सहेजे गए बिंदु को तब तक खोलेगा, जब तक कि उस नाम का कोई भी हिस्सा शेष न हो। उसके बाद, उस नाम का एक ROLLBACK जारी किया जाएगा, जो सभी लेनदेन को रोलबैक करेगा।
    • उदाहरण के लिए, निम्न कमांड दिखाए गए क्रम में चलाए गए थे:

      BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4
      

      अब, यदि आप जारी करते हैं (निम्नलिखित परिदृश्य में से प्रत्येक एक दूसरे से स्वतंत्र है):

      • ROLLBACK TRAN Bएक बार: यह "डीएमएल क्वेरी 4" को पूर्ववत कर देगा। @@TRANCOUNTअभी भी 2 है।
      • ROLLBACK TRAN Bदो बार: यह "डीएमएल क्वेरी 4" को पूर्ववत् करेगा और फिर त्रुटि "बी" के लिए कोई समान बचत बिंदु नहीं है। @@TRANCOUNTअभी भी 2 है।
      • ROLLBACK TRAN Aएक बार: यह "डीएमएल क्वेरी 4" और "डीएमएल क्वेरी 3" को पूर्ववत कर देगा। @@TRANCOUNTअभी भी 2 है।
      • ROLLBACK TRAN Aदो बार: यह "डीएमएल क्वेरी 4", "डीएमएल क्वेरी 3" और "डीएमएल क्वेरी 2" को पूर्ववत् करेगा। @@TRANCOUNTअभी भी 2 है।
      • ROLLBACK TRAN Aतीन बार: यह "डीएमएल क्वेरी 4", "डीएमएल क्वेरी 3", और "डीएमएल क्वेरी 2" को पूर्ववत करेगा। फिर यह पूरे लेनदेन को रोलबैक करेगा (जो सब छोड़ दिया गया था "डीएमएल क्वेरी 1")। @@TRANCOUNTअब 0 है।
      • COMMITएक बार: @@TRANCOUNT1 से नीचे जाता है।
      • COMMITएक बार और फिर ROLLBACK TRAN Bएक बार: @@TRANCOUNT1 से नीचे जाता है। फिर यह "डीएमएल क्वेरी 4" को पूर्ववत कर देगा (यह साबित करते हुए कि COMMIT ने कुछ भी नहीं किया है)। @@TRANCOUNTअभी भी 1 है।
  • लेनदेन के नाम और बिंदुओं को बचाने के लिए

  • एक संग्रहीत प्रक्रिया अपने आप में, एक निहित लेनदेन नहीं है। यदि कोई स्पष्ट लेनदेन शुरू नहीं किया गया है, तो प्रत्येक क्वेरी एक अंतर्निहित लेनदेन है। यही कारण है कि एकल प्रश्नों के आसपास स्पष्ट लेन-देन आवश्यक नहीं है जब तक कि ए करने के लिए कोई प्रोग्रामेटिक कारण नहीं हो सकता है ROLLBACK, अन्यथा क्वेरी में कोई भी त्रुटि उस क्वेरी का एक स्वचालित रोलबैक है।

  • संग्रहीत प्रक्रिया को कॉल करते समय, इसे @@TRANCOUNTउसी मूल्य के साथ बाहर निकलना चाहिए जब इसे बुलाया गया था। मतलब, आप नहीं कर सकते:

    • BEGIN TRANकॉलिंग / पैरेंट प्रोसेस में कमिटमेंट की उम्मीद किए बिना इसे प्रोक्योर करें ।
    • ROLLBACKयदि कोई स्पष्ट लेन-देन शुरू होने से पहले शुरू किया गया था तो आप इसे जारी नहीं कर सकते क्योंकि इसे @@TRANCOUNT0 पर लौटाया जाएगा ।

    यदि आप किसी संग्रहीत कार्यविधि से संग्रहीत कार्यविधि से बाहर निकलते हैं जो या तो उच्च या उससे कम है जब वह घूरता है, तो आपको इसके समान त्रुटि मिलेगी:

    Msg 266, लेवल 16, स्टेट 2, प्रोसीजर YourProcName, लाइन 0
    EXECUTE के बाद ट्रांजेक्शन काउंट BEGIN और COMMIT स्टेटमेंट के बेमेल नंबर को दर्शाता है। पिछली संख्या = एक्स, वर्तमान गणना = वाई।

  • टेबल चर, नियमित चर की तरह, लेनदेन से बाध्य नहीं हैं।


प्रॉक्स में लेनदेन से निपटने के बारे में जिसे या तो स्वतंत्र रूप से कहा जा सकता है (और इसलिए लेनदेन से निपटने की आवश्यकता है) या अन्य प्रोक्स से कॉल करें (इसलिए लेनदेन से निपटने की आवश्यकता नहीं है): इसे कुछ अलग तरीकों से पूरा किया जा सकता है।

जिस तरह से है कि अब मैं कई वर्षों के लिए यह से निपटने की है अच्छी तरह से काम करने लगता है कि केवल करने के लिए है BEGIN/ COMMIT/ ROLLBACKपर बाहरी-ऊपरी स्तर। उप-खरीदारी कॉल केवल लेनदेन आदेशों को छोड़ देती हैं। मैंने प्रत्येक खरीद में (जो हर एक को लेनदेन से निपटने की जरूरत है) नीचे दिया है।

  • प्रत्येक खरीद के शीर्ष पर, DECLARE @InNestedTransaction BIT;
  • सरल के स्थान पर BEGIN TRAN, करें:

    IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
  • सरल के स्थान पर COMMIT, करें:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • सरल के स्थान पर ROLLBACK, करें:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;

यह तरीका समान होना चाहिए, भले ही SQL सर्वर के भीतर लेन-देन शुरू किया गया हो या ऐप परत पर शुरू किया गया हो।

TRY...CATCHनिर्माण के भीतर इस लेन-देन से निपटने के पूर्ण खाके के लिए , कृपया निम्नलिखित DBA.SE प्रश्न के लिए मेरा उत्तर देखें: क्या हमें C # कोड में लेन-देन के साथ-साथ संग्रहीत कार्यविधि को भी संभालने की आवश्यकता है


"मूल बातें" से आगे बढ़ते हुए, लेन-देन की कुछ अतिरिक्त बारीकियों के बारे में पता होना चाहिए:

  • डिफ़ॉल्ट रूप से, लेन-देन, अधिकांश समय होता है, जब कोई त्रुटि होती है, तो स्वचालित रूप से रोल-बैक / रद्द नहीं किया जाता है। यह आमतौर पर एक समस्या नहीं है जब तक आपके पास उचित त्रुटि से निपटने और ROLLBACKअपने आप को कॉल करने के लिए नहीं है । हालांकि, कभी-कभी चीजें जटिल हो जाती हैं, जैसे कि बैच-एबॉर्टिंग त्रुटियों के मामले में, या जब OPENQUERY(या सामान्य रूप से लिंक्ड सर्वर) का उपयोग करते हुए और रिमोट सिस्टम पर कोई त्रुटि होती है। हालांकि अधिकांश त्रुटियों का उपयोग करके फंसे हुए हो सकते हैं TRY...CATCH, दो ऐसे हैं जो उस तरह से नहीं फंस सकते हैं (यह याद नहीं है कि इस समय कौन से हैं, हालांकि - शोध)। इन मामलों में, आपको SET XACT_ABORT ONलेन-देन को ठीक से रोलबैक करने के लिए उपयोग करना चाहिए ।

    SET XACT_ABORT के कारण SQL सर्वर तुरंत किसी भी Transaction (यदि कोई सक्रिय है) को वापस कर देता है और कोई भी त्रुटि होने पर बैच को निरस्त कर देता है। SQL सर्वर 2005 से पहले यह सेटिंग मौजूद थी, जिसने TRY...CATCHनिर्माण शुरू किया था। अधिकांश भाग के लिए, TRY...CATCHअधिकांश स्थितियों को संभालता है और इसलिए ज्यादातर इसकी आवश्यकता को पूरा करता है XACT_ABORT ON। हालांकि, जब उपयोग OPENQUERY(और संभवतः एक अन्य परिदृश्य जिसे मैं इस समय याद नहीं कर सकता हूं), तो आपको अभी भी उपयोग करने की आवश्यकता होगी SET XACT_ABORT ON;

  • ट्रिगर के अंदर, XACT_ABORTअंतर्निहित रूप से सेट किया गया है ON। यह ट्रिगर के भीतर किसी भी त्रुटि का कारण बनता है जिससे ट्रिगर को निकाल दिया गया संपूर्ण DML कथन रद्द हो जाता है।

  • आपके पास हमेशा उचित त्रुटि हैंडलिंग होनी चाहिए, विशेष रूप से लेन-देन का उपयोग करते समय। TRY...CATCHनिर्माण, SQL सर्वर 2005 में शुरू की, लगभग सभी स्थितियों, के लिए परीक्षण पर एक स्वागत सुधार से निपटने में एक साधन प्रदान करता @@ERRORप्रत्येक बयान है, जो बैच-निरस्त त्रुटियों के साथ ज्यादा मदद नहीं की है।

    TRY...CATCHहालांकि, एक नया "राज्य" शुरू किया। जब नहीं का उपयोग कर TRY...CATCHनिर्माण, अगर आप एक सक्रिय लेन-देन है और एक त्रुटि तब होती है, तो कई रास्ते है कि लिया जा सकता है:

    • XACT_ABORT OFFऔर कथन-निरस्त करने की त्रुटि: लेन-देन अभी भी सक्रिय है और यदि कोई हो, तो अगले कथन के साथ प्रसंस्करण जारी है ।
    • XACT_ABORT OFFऔर सबसे बैच-गर्भपात की त्रुटियां: लेन-देन अभी भी सक्रिय है और अगले बैच के साथ प्रसंस्करण जारी है , यदि कोई हो।
    • XACT_ABORT OFFऔर कुछ बैच-गर्भपात की त्रुटियां: लेन-देन लुढ़का हुआ है और अगले बैच के साथ प्रसंस्करण जारी है , यदि कोई हो।
    • XACT_ABORT ONऔर कोई त्रुटि: लेन-देन लुढ़का हुआ है और अगले बैच के साथ प्रसंस्करण जारी है , यदि कोई हो।


    जब भी, TRY...CATCHबैच-गर्भपात की त्रुटियों का उपयोग करते हुए , बैच को निरस्त न करें, बल्कि CATCHब्लॉक पर नियंत्रण स्थानांतरित करें । कब XACT_ABORTहै OFF, लेन-देन अभी भी अधिकांश समय तक सक्रिय रहेगा, और आपको COMMITसबसे अधिक संभावना होगी , या ROLLBACK। लेकिन जब कुछ बैच-एबॉर्टिंग एरर (जैसे कि OPENQUERY), या जब XACT_ABORTहो ON, का सामना करना पड़ता है , तो ट्रांजेक्शन एक नई स्थिति में होगा, "अनबिकेबल"। इस अवस्था में आप COMMITन तो कोई DML ऑपरेशन कर सकते हैं और न ही कर सकते हैं। आप सभी कर सकते हैं ROLLBACKऔर SELECTबयान। हालाँकि, इस "असुविधाजनक" स्थिति में, लेन-देन त्रुटि होने पर वापस ले लिया गया था, और इसे जारी करना ROLLBACKकेवल एक औपचारिकता है, लेकिन एक ऐसा किया जाना चाहिए।

    एक फ़ंक्शन, XACT_STATE , का उपयोग यह निर्धारित करने के लिए किया जा सकता है कि क्या लेन-देन सक्रिय , असुविधाजनक है या मौजूद नहीं है। यह CATCHनिर्धारित करने के लिए कि -1परीक्षण के बजाय परिणाम (यानी असुविधाजनक) है, तो यह निर्धारित करने के लिए ब्लॉक में इस फ़ंक्शन की जांच करने के लिए (कुछ, कम से कम) की सिफारिश की जाती है @@TRANCOUNT > 0। लेकिन इसके साथ XACT_ABORT ON, यह एकमात्र संभव राज्य होना चाहिए, इसलिए ऐसा लगता है कि इसके लिए परीक्षण @@TRANCOUNT > 0और XACT_STATE() <> 0समकक्ष हैं। दूसरी ओर, जब XACT_ABORTहोता है OFFऔर एक सक्रिय लेनदेन होता है, तो 1या तो या ब्लॉक -1में एक राज्य होना संभव है CATCH, जो COMMITइसके बजाय जारी करने की संभावना के लिए अनुमति देता है ROLLBACK(हालांकि, मैं किसी के लिए एक मामले के बारे में नहीं सोच सकता करना चाहेगाCOMMITयदि लेनदेन सराहनीय है)। अधिक जानकारी और प्रयोग पर अनुसंधान XACT_STATE()एक के भीतर CATCHके साथ ब्लॉक XACT_ABORT ON: निम्नलिखित DBA.SE सवाल का मेरा उत्तर में पाया जा सकता में क्या मामलों एक सौदे कैच ब्लॉक के अंदर से प्रतिबद्ध किया जा सकता है जब XACT_ABORT चालू पर सेट है? । कृपया ध्यान दें कि इसके साथ एक मामूली बग है XACT_STATE()जो इसे 1कुछ परिदृश्यों में गलत तरीके से वापस करने का कारण बनता है: XACT_STATE () रिटर्न 1 जब कुछ सिस्टम चर के साथ चयन में उपयोग किया जाता है, लेकिन बिना खंड से


मूल कोड के बारे में नोट्स:

  • आप लेन-देन को दिया गया नाम हटा सकते हैं क्योंकि यह कोई मदद नहीं कर रहा है।
  • आपको प्रत्येक कॉल पर BEGINऔर ENDउसके आसपास की आवश्यकता नहीं हैEXEC

2
यह वास्तव में अच्छा, अच्छा, उत्तर है।
मैकनेट्स

1
वाह, यह एक व्यापक जवाब है! धन्यवाद! BTW डॉस निम्नलिखित पृष्ठ को संबोधित करता है जो आपके द्वारा किए गए त्रुटियों को ट्राई करते हैं ... ट्रैप ... कैच? (शीर्षक "एक का प्रयास करें ... कैच करें संरचना से अप्रभावित त्रुटियाँ" के तहत? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba

1
@jrdevdba धन्यवाद :-) और yer का स्वागत है। फँस नहीं त्रुटियों के बारे में, मैं बहुत ज्यादा इन दोनों का मतलब था: Compile errors, such as syntax errors, that prevent a batch from runningऔर Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.। लेकिन वे बहुत बार नहीं होते हैं, और जब आप ऐसी स्थिति पाते हैं, तो या तो इसे ठीक करें (यदि यह कोड में बग है) या इसे एक उप-प्रक्रिया ( EXECया sp_executesql) में रखें ताकि TRY...CATCHयह फंस जाए।
सोलोमन रटज़की

2

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

यहां तक ​​कि अगर आपने अपनी नेस्टेड संग्रहीत प्रक्रियाओं में कोई स्पष्ट लेनदेन लागू नहीं किया है, तब भी ये संग्रहीत प्रक्रिया अंतर्निहित लेनदेन का उपयोग करेगी और बीयूटी को पूरा करने पर प्रतिबद्ध होगी या तो आप नेस्टेड संग्रहीत प्रक्रियाओं में स्पष्ट या अंतर्निहित लेनदेन के माध्यम से प्रतिबद्ध हैं SQL सर्वर इंजन इसे अनदेखा करेगा और करेगा इन संग्रहीत नेस्टेड प्रक्रियाओं के सभी कार्यों को रोलबैक करें यदि मास्टर संग्रहीत प्रक्रिया विफल हो जाती है और लेनदेन रोल-समर्थित है।

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

संदर्भ के लिए http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.