क्या एक INSERT और SELECT की तुलना में OUTPUT के साथ बेहतर अभ्यास है?


12

हम अक्सर "यदि मौजूद नहीं है, तो डालें" स्थिति का सामना करते हैं। डैन गुज़मैन के ब्लॉग की एक उत्कृष्ट जांच है कि इस प्रक्रिया को थ्रेडसेफ़ कैसे बनाया जाए।

मेरे पास एक मूल तालिका है जो एक स्ट्रिंग को पूर्णांक से पूर्णांक में सूचीबद्ध करती है SEQUENCE। एक संग्रहीत कार्यविधि में मुझे या तो मौजूद मूल्य के लिए पूर्णांक कुंजी प्राप्त करने की आवश्यकता है, या INSERTयह और फिर परिणामी मान प्राप्त करें। dbo.NameLookup.ItemNameस्तंभ पर एक विशिष्ट बाधा है, इसलिए डेटा अखंडता जोखिम में नहीं है, लेकिन मैं अपवादों का सामना नहीं करना चाहता।

यह नहीं है IDENTITYइसलिए मुझे नहीं मिल SCOPE_IDENTITYसकता है और मूल्य NULLकुछ मामलों में हो सकता है।

मेरी स्थिति में मुझे केवल INSERTमेज पर सुरक्षा से निपटना है , इसलिए मैं यह तय करने की कोशिश कर रहा हूं कि क्या MERGEइस तरह का उपयोग करना बेहतर है :

SET NOCOUNT, XACT_ABORT ON;

DECLARE @vValueId INT 
DECLARE @inserted AS TABLE (Id INT NOT NULL)

MERGE 
    dbo.NameLookup WITH (HOLDLOCK) AS f 
USING 
    (SELECT @vName AS val WHERE @vName IS NOT NULL AND LEN(@vName) > 0) AS new_item
        ON f.ItemName= new_item.val
WHEN MATCHED THEN
    UPDATE SET @vValueId = f.Id
WHEN NOT MATCHED BY TARGET THEN
    INSERT
      (ItemName)
    VALUES
      (@vName)
OUTPUT inserted.Id AS Id INTO @inserted;
SELECT @vValueId = s.Id FROM @inserted AS s

मैं MERGEसिर्फ एक सशर्त का उपयोग करके इस बुद्धि को कर सकता था, INSERTजिसके बाद SELECT मुझे लगता है कि यह दूसरा दृष्टिकोण पाठक के लिए स्पष्ट है, लेकिन मुझे यकीन नहीं है कि यह "बेहतर" अभ्यास है

SET NOCOUNT, XACT_ABORT ON;

INSERT INTO 
    dbo.NameLookup (ItemName)
SELECT
    @vName
WHERE
    NOT EXISTS (SELECT * FROM dbo.NameLookup AS t WHERE @vName IS NOT NULL AND LEN(@vName) > 0 AND t.ItemName = @vName)

DECLARE @vValueId int;
SELECT @vValueId = i.Id FROM dbo.NameLookup AS i WHERE i.ItemName = @vName

या शायद एक और बेहतर तरीका है जिसे मैंने नहीं माना है

मैंने अन्य प्रश्नों की खोज और संदर्भ किया। यह एक: /programming/5288283/sql-server-insert-if-not-exists-best-ults सबसे उपयुक्त है जो मुझे मिल सकता है लेकिन मेरे उपयोग के मामले में बहुत लागू नहीं लगता है। IF NOT EXISTS() THENदृष्टिकोण के अन्य प्रश्न जो मुझे नहीं लगता कि स्वीकार्य है।


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

जवाबों:


8

चूँकि आप अनुक्रम का उपयोग कर रहे हैं, आप उसी NEXT VALUE फ़ॉर फ़ंक्शन का उपयोग कर सकते हैं - जो आपके पास Idप्राथमिक कुंजी फ़ील्ड पर पहले से ही डिफ़ॉल्ट बाधा है - Idसमय से पहले एक नया मान उत्पन्न करने के लिए । पहले मूल्य उत्पन्न करने का मतलब है कि आपको नहीं होने के बारे में चिंता करने की आवश्यकता नहीं है SCOPE_IDENTITY, जिसका अर्थ है कि आपको नए मान प्राप्त करने के लिए OUTPUTया तो खंड या अतिरिक्त SELECTकरने की आवश्यकता नहीं है ; आपके द्वारा करने से पहले आपके पास मूल्य होगा INSERT, और आपको SET IDENTITY INSERT ON / OFF:-) के साथ गड़बड़ करने की आवश्यकता नहीं है

ताकि समग्र स्थिति के हिस्से का ध्यान रखा जाए। दूसरा भाग दो प्रक्रियाओं के समवर्ती मुद्दे को संभाल रहा है, ठीक उसी समय, ठीक उसी स्ट्रिंग के लिए एक मौजूदा पंक्ति नहीं ढूंढ रहा है, और इसके साथ आगे बढ़ रहा है INSERT। चिंता अद्वितीय बाधा उल्लंघन से बचने के बारे में है जो घटित होगी।

इस प्रकार के समसामयिक मुद्दों को संभालने का एक तरीका यह है कि इस विशेष ऑपरेशन को सिंगल थ्रेडेड करने के लिए बाध्य किया जाए। ऐसा करने का तरीका एप्लिकेशन लॉक का उपयोग करके है (जो सत्रों में काम करता है)। प्रभावी होने के दौरान, वे इस तरह की स्थिति के लिए थोड़े भारी हो सकते हैं जहां टकराव की आवृत्ति संभवतः काफी कम है।

टकरावों से निपटने का दूसरा तरीका यह है कि वे स्वीकार करें कि वे कभी-कभी होते हैं और उनसे बचने की कोशिश करने के बजाय उन्हें संभालते हैं। TRY...CATCHनिर्माण का उपयोग करते हुए , आप प्रभावी रूप से एक विशिष्ट त्रुटि (इस मामले में: "अद्वितीय बाधा उल्लंघन", Msg 2601) में फंस सकते SELECTहैं और Idमूल्य प्राप्त करने के लिए फिर से निष्पादित कर सकते हैं क्योंकि हम जानते हैं कि यह अब CATCHउस विशेष के साथ ब्लॉक में होने के कारण मौजूद है त्रुटि। अन्य त्रुटियों को विशिष्ट RAISERROR/ RETURNया THROWतरीके से नियंत्रित किया जा सकता है।

टेस्ट सेटअप: अनुक्रम, तालिका और अद्वितीय सूचकांक

USE [tempdb];

CREATE SEQUENCE dbo.MagicNumber
  AS INT
  START WITH 1
  INCREMENT BY 1;

CREATE TABLE dbo.NameLookup
(
  [Id] INT NOT NULL
         CONSTRAINT [PK_NameLookup] PRIMARY KEY CLUSTERED
        CONSTRAINT [DF_NameLookup_Id] DEFAULT (NEXT VALUE FOR dbo.MagicNumber),
  [ItemName] NVARCHAR(50) NOT NULL         
);

CREATE UNIQUE NONCLUSTERED INDEX [UIX_NameLookup_ItemName]
  ON dbo.NameLookup ([ItemName]);
GO

टेस्ट सेटअप: संग्रहीत प्रक्रिया

CREATE PROCEDURE dbo.GetOrInsertName
(
  @SomeName NVARCHAR(50),
  @ID INT OUTPUT,
  @TestRaceCondition BIT = 0
)
AS
SET NOCOUNT ON;

BEGIN TRY
  SELECT @ID = nl.[Id]
  FROM   dbo.NameLookup nl
  WHERE  nl.[ItemName] = @SomeName
  AND    @TestRaceCondition = 0;

  IF (@ID IS NULL)
  BEGIN
    SET @ID = NEXT VALUE FOR dbo.MagicNumber;

    INSERT INTO dbo.NameLookup ([Id], [ItemName])
    VALUES (@ID, @SomeName);
  END;
END TRY
BEGIN CATCH
  IF (ERROR_NUMBER() = 2601) -- "Cannot insert duplicate key row in object"
  BEGIN
    SELECT @ID = nl.[Id]
    FROM   dbo.NameLookup nl
    WHERE  nl.[ItemName] = @SomeName;
  END;
  ELSE
  BEGIN
    ;THROW; -- SQL Server 2012 or newer
    /*
    DECLARE @ErrorNumber INT = ERROR_NUMBER(),
            @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();

    RAISERROR(N'Msg %d: %s', 16, 1, @ErrorNumber, @ErrorMessage);
    RETURN;
    */
  END;

END CATCH;
GO

कसौटी

DECLARE @ItemID INT;
EXEC dbo.GetOrInsertName
  @SomeName = N'test1',
  @ID = @ItemID OUTPUT;
SELECT @ItemID AS [ItemID];
GO

DECLARE @ItemID INT;
EXEC dbo.GetOrInsertName
  @SomeName = N'test1',
  @ID = @ItemID OUTPUT,
  @TestRaceCondition = 1;
SELECT @ItemID AS [ItemID];
GO

ओपी से सवाल

इससे बेहतर क्यों है MERGE? क्या मुझे क्लॉज TRYका उपयोग करके समान कार्यक्षमता नहीं मिलेगी WHERE NOT EXISTS?

MERGEविभिन्न "मुद्दे" हैं (कई संदर्भ @ SqlZim के उत्तर से जुड़े हुए हैं, इसलिए उस जानकारी को डुप्लिकेट करने की कोई आवश्यकता नहीं है)। और, इस दृष्टिकोण (कम विवाद) में कोई अतिरिक्त लॉकिंग नहीं है, इसलिए इसे संगामिति पर बेहतर होना चाहिए। इस दृष्टिकोण में, आपको कभी भी एक अद्वितीय बाधा का उल्लंघन नहीं मिलेगा, सभी बिना किसी HOLDLOCKआदि के, यह काम करने की बहुत अधिक गारंटी है।

इस दृष्टिकोण के पीछे तर्क है:

  1. यदि आपके पास इस प्रक्रिया का पर्याप्त निष्पादन है जैसे कि आपको टकरावों के बारे में चिंता करने की आवश्यकता है, तो आप यह नहीं करना चाहते हैं:
    1. आवश्यकता से अधिक कदम उठाना
    2. आवश्यकता से अधिक समय तक किसी भी संसाधन पर ताले रखें
  2. चूंकि टकराव केवल नई प्रविष्टियों पर हो सकता है ( बिल्कुल उसी समय प्रस्तुत की गई नई प्रविष्टियाँ ), CATCHपहली जगह में ब्लॉक में गिरने की आवृत्ति बहुत कम होगी। यह उस कोड को ऑप्टिमाइज़ करने के लिए अधिक समझ में आता है जो उस कोड के बजाय 99% चलाएगा जो 1% समय तक चलेगा (जब तक कि दोनों को अनुकूलित करने की कोई लागत नहीं है, लेकिन यहां ऐसा नहीं है)।

@ SqlZim के उत्तर से टिप्पणी (जोर दिया गया)

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

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

इस दृष्टिकोण के साथ मैं जो मुद्दा देख रहा हूं वह यह है कि यह सुझाव दिए जाने की तुलना में अधिक लॉक है। "क्रमबद्ध" पर उद्धृत दस्तावेज को फिर से पढ़ना महत्वपूर्ण है, विशेष रूप से निम्नलिखित (जोर दिया गया):

  • अन्य लेनदेन मुख्य मूल्यों के साथ नई पंक्तियाँ नहीं डाल सकते हैं जो वर्तमान लेनदेन में किसी भी बयान द्वारा पढ़ी गई कुंजियों की श्रेणी में आते हैं जब तक कि वर्तमान लेनदेन पूरा नहीं हो जाता।

अब, यहाँ उदाहरण कोड में टिप्पणी है:

SELECT [Id]
FROM   dbo.NameLookup WITH (SERIALIZABLE) /* hold that key range for @vName */

ऑपरेटिव शब्द "रेंज" है। लिया जा रहा ताला केवल मूल्य पर नहीं है @vName, लेकिन अधिक सटीक रूप से शुरू होने वाली सीमा हैवह स्थान जहाँ यह नया मान जाना चाहिए (अर्थात जहाँ नया मान फ़िट होता है, उसके दोनों ओर मौजूदा प्रमुख मानों के बीच), लेकिन स्वयं मान नहीं। वर्तमान में वर्तमान में देखे जा रहे मूल्य के आधार पर नए मूल्यों को सम्मिलित करने से अन्य प्रक्रियाओं को अवरुद्ध किया जाएगा। यदि लुकअप रेंज के शीर्ष पर किया जा रहा है, तो ऐसी किसी भी चीज़ को सम्मिलित करना जो उस स्थान को अवरुद्ध कर सकती है। उदाहरण के लिए, यदि मान "a", "b", और "d" मौजूद हैं, तो यदि एक प्रक्रिया "f" पर SELECT कर रही है, तो मान "g" या "e" प्रविष्ट करना संभव नहीं होगा ( चूँकि उनमें से कोई भी "d") के तुरंत बाद आएगा। लेकिन, "c" का मान सम्मिलित करना संभव होगा क्योंकि इसे "आरक्षित" श्रेणी में नहीं रखा जाएगा।

निम्नलिखित उदाहरण को इस व्यवहार को चित्रित करना चाहिए:

(क्वेरी टैब में (सत्र) # 1)

INSERT INTO dbo.NameLookup ([ItemName]) VALUES (N'test5');

BEGIN TRAN;

SELECT [Id]
FROM   dbo.NameLookup WITH (SERIALIZABLE) /* hold that key range for @vName */
WHERE  ItemName = N'test8';

--ROLLBACK;

(क्वेरी टैब में (सत्र) # 2)

EXEC dbo.NameLookup_getset_byName @vName = N'test4';
-- works just fine

EXEC dbo.NameLookup_getset_byName @vName = N'test9';
-- hangs until you either hit "cancel" in this query tab,
-- OR issue a COMMIT or ROLLBACK in query tab #1

EXEC dbo.NameLookup_getset_byName @vName = N'test7';
-- hangs until you either hit "cancel" in this query tab,
-- OR issue a COMMIT or ROLLBACK in query tab #1

EXEC dbo.NameLookup_getset_byName @vName = N's';
-- works just fine

EXEC dbo.NameLookup_getset_byName @vName = N'u';
-- hangs until you either hit "cancel" in this query tab,
-- OR issue a COMMIT or ROLLBACK in query tab #1

इसी तरह, यदि मूल्य "C" मौजूद है, और "A" का चयन किया जा रहा है, और (इसलिए लॉक किया गया है), तो आप "D" का मान डाल सकते हैं, लेकिन "B" का मान नहीं:

(क्वेरी टैब में (सत्र) # 1)

INSERT INTO dbo.NameLookup ([ItemName]) VALUES (N'testC');

BEGIN TRAN

SELECT [Id]
FROM   dbo.NameLookup WITH (SERIALIZABLE) /* hold that key range for @vName */
WHERE  ItemName = N'testA';

--ROLLBACK;

(क्वेरी टैब में (सत्र) # 2)

EXEC dbo.NameLookup_getset_byName @vName = N'testD';
-- works just fine

EXEC dbo.NameLookup_getset_byName @vName = N'testB';
-- hangs until you either hit "cancel" in this query tab,
-- OR issue a COMMIT or ROLLBACK in query tab #1

निष्पक्ष होने के लिए, मेरे सुझाए गए दृष्टिकोण में, जब कोई अपवाद होता है, तो लेन-देन लॉग में 4 प्रविष्टियां होंगी जो इस "क्रमबद्ध लेनदेन" दृष्टिकोण में नहीं होंगी। लेकिन, जैसा कि मैंने ऊपर कहा है, अगर अपवाद समय का 1% (या 5%) भी होता है, जो कि प्रारंभिक चयन को अस्थायी रूप से अवरुद्ध INSERT संचालन के कहीं अधिक संभावित मामले की तुलना में बहुत कम प्रभाव डालता है।

एक और, यद्यपि मामूली, इस "क्रमबद्ध लेनदेन + OUTPUT क्लॉज" दृष्टिकोण के साथ मुद्दा यह है कि OUTPUTक्लॉज (इसके वर्तमान उपयोग में) परिणाम सेट के रूप में डेटा वापस भेजता है। एक परिणाम सेट के लिए एक साधारण OUTPUTपैरामीटर की तुलना में अधिक ओवरहेड (संभवतः दोनों तरफ: SQL सर्वर में आंतरिक कर्सर को प्रबंधित करने के लिए, और ऐप लेयर में DataReader ऑब्जेक्ट को प्रबंधित करने के लिए) की आवश्यकता होती है । यह देखते हुए कि हम केवल एकल स्केलर मान के साथ काम कर रहे हैं, और यह अनुमान निष्पादन की एक उच्च आवृत्ति है, कि परिणाम सेट का अतिरिक्त ओवरहेड संभवतः जोड़ता है।

जबकि OUTPUTक्लॉज का उपयोग इस तरह किया जा सकता है OUTPUTकि पैरामीटर को वापस किया जा सकता है , जिसके लिए एक अस्थायी टेबल या टेबल वैरिएबल बनाने के लिए अतिरिक्त चरणों की आवश्यकता होगी, और फिर उस टेम्‍प टेबल / टेबल वैरिएबल के मान को OUTPUTपैरामीटर में से चुनने के लिए ।

इसके अलावा स्पष्टीकरण: मेरे जवाब के लिए @ SqlZim की प्रतिक्रिया (अद्यतन उत्तर) @ SqlZim की प्रतिक्रिया (मूल उत्तर में) के लिए मेरे वक्तव्य में संगामिति और प्रदर्शन;;

क्षमा करें यदि यह हिस्सा एक लंबा-चौड़ा है, लेकिन इस बिंदु पर हम दो दृष्टिकोणों की बारीकियों के लिए नीचे हैं।

मेरा मानना ​​है कि जिस तरह से जानकारी serializableप्रस्तुत की गई है, वह मूल प्रश्न में प्रस्तुत परिदृश्य के रूप में उपयोग करते समय किसी से मुठभेड़ की उम्मीद कर सकती है ।

हां, मैं मानता हूं कि मैं पक्षपाती हूं, हालांकि निष्पक्ष होना चाहिए:

  1. मानव के लिए यह असंभव नहीं है कि वह कम से कम कुछ छोटी डिग्री का पक्षपाती हो, और मैं उसे कम से कम रखने की कोशिश करूं,
  2. जो उदाहरण दिया गया था वह सरल था, लेकिन यह व्यवहार को बिना जटिल किए उसे स्पष्ट करने के लिए था। अत्यधिक आवृत्ति का इरादा नहीं था, हालांकि मैं समझता हूं कि मैंने भी स्पष्ट रूप से राज्य नहीं किया था और इसे वास्तव में मौजूद की तुलना में बड़ी समस्या के रूप में पढ़ा जा सकता है। मैं नीचे स्पष्ट करने का प्रयास करूंगा।
  3. मैंने दो मौजूदा कुंजियों ("क्वेरी टैब 1 का दूसरा सेट" और "क्वेरी टैब 2" ब्लॉक) के बीच एक सीमा को लॉक करने का एक उदाहरण भी शामिल किया।
  4. मुझे अपने दृष्टिकोण की "छिपी हुई लागत" का पता चला (और स्वयंसेवक), कि INSERTएक अद्वितीय बाधा उल्लंघन के कारण हर बार चार अतिरिक्त ट्रैन लॉग प्रविष्टियाँ विफल हो जाती हैं। मैंने ऐसा किसी अन्य उत्तर / पोस्ट में नहीं देखा है।

@ Gbn के "JFDI" दृष्टिकोण के बारे में, माइकल जे। स्वार्ट की "अग्ली प्रैग्मटिज्म फॉर द विन" पोस्ट, और माइकल की पोस्ट पर आरोन बर्ट्रैंड की टिप्पणी (उनके परीक्षण से पता चलता है कि प्रदर्शन में क्या कमी आई है), और आपकी टिप्पणी "माइकल जे के अनुकूलन" पर। । स्टीवर्ट के @ gbn की कोशिश पकड़ो JFDI प्रक्रिया को "बताते हुए कहा:"

यदि आप मौजूदा मानों के चयन की तुलना में अधिक बार नए मान सम्मिलित कर रहे हैं, तो यह @ srutzky के संस्करण से अधिक प्रदर्शनकारी हो सकता है। अन्यथा मैं इस पर @ srutzky के संस्करण को पसंद करूंगा।

"JFDI" दृष्टिकोण से संबंधित उस gbn / माइकल / आरोन चर्चा के संबंध में, यह मेरे सुझाव को gbn के "JFDI" दृष्टिकोण के बराबर करना गलत होगा। "जाओ या सम्मिलित करें" ऑपरेशन की प्रकृति के कारण, मौजूदा रिकॉर्ड के लिए मूल्य SELECTप्राप्त करने के लिए एक स्पष्ट आवश्यकता है ID। यह चयन IF EXISTSचेक के रूप में कार्य करता है , जो इस दृष्टिकोण को आरोन के परीक्षणों के "चेकट्रीकैच" भिन्नता के समान बनाता है। माइकल का फिर से लिखा कोड (और माइकल के अनुकूलन का आपका अंतिम रूपांतर) में एक WHERE NOT EXISTSऐसा ही चेक पहले करना भी शामिल है । इसलिए, मेरा सुझाव (माइकल के अंतिम कोड और उनके अंतिम कोड के आपके अनुकूलन के साथ) वास्तव में CATCHब्लॉक को अक्सर नहीं मारा जाएगा । यह केवल दो सत्रों की स्थिति हो सकती है,ItemNameINSERT...SELECTठीक उसी क्षण जैसे कि दोनों सत्रों को ठीक उसी क्षण के लिए एक "सत्य" प्राप्त होता है WHERE NOT EXISTSऔर इस प्रकार दोनों एक ही क्षण में सही करने का प्रयास करते INSERTहैं। यह बहुत विशिष्ट परिदृश्य बहुत कम बार होता है या तो किसी मौजूदा का चयन करने ItemNameया किसी नए को सम्मिलित करने के दौरान ItemNameजब कोई अन्य प्रक्रिया सटीक समय पर ऐसा करने का प्रयास नहीं करती है ।

मन में सभी के साथ: मैं अपने दृष्टिकोण को क्यों पसंद करूं?

सबसे पहले, आइए देखें कि "क्रमबद्ध" दृष्टिकोण में लॉकिंग क्या होता है। जैसा कि ऊपर उल्लेख किया गया है, "लॉक" जो बंद हो जाता है वह मौजूदा कुंजी मूल्यों के दोनों तरफ निर्भर करता है जहां नया कुंजी मूल्य फिट होगा। रेंज की शुरुआत या अंत क्रमशः इंडेक्स की शुरुआत या अंत भी हो सकता है, अगर उस दिशा में कोई मौजूदा कुंजी मूल्य नहीं है। मान लें कि हमारे पास निम्नलिखित सूचकांक और कुंजियाँ हैं ( ^जबकि $इसके अंत का प्रतिनिधित्व करते हुए सूचकांक की शुरुआत का प्रतिनिधित्व करता है):

Range #:    |--- 1 ---|--- 2 ---|--- 3 ---|--- 4 ---|
Key Value:  ^         C         F         J         $

यदि सत्र 55 एक प्रमुख मूल्य डालने का प्रयास करता है:

  • A, तो # 1 (से लेकर ^के लिए C) बंद कर दिया जाता है: सत्र 56 के एक मूल्य नहीं डाल सकते Bहैं, भले ही अनोखी और वैध (अभी तक)। लेकिन सत्र 56 , और D, के मूल्यों को सम्मिलित कर सकता है ।GM
  • D, तो # 2 (से लेकर Cके लिए F) बंद कर दिया जाता है: सत्र 56 के एक मूल्य नहीं डाल सकते E(अभी तक)। लेकिन सत्र 56 , और A, के मूल्यों को सम्मिलित कर सकता है ।GM
  • M, तो # 4 (से लेकर Jके लिए $) बंद कर दिया जाता है: सत्र 56 के एक मूल्य नहीं डाल सकते X(अभी तक)। लेकिन सत्र 56 , और A, के मूल्यों को सम्मिलित कर सकता है ।DG

जैसे-जैसे अधिक महत्वपूर्ण मान जोड़े जाते हैं, कुंजी मानों के बीच की श्रृंखला संकरी होती जाती है, इसलिए एक ही समय में लड़ते हुए एक ही समय में डाले जा रहे कई मानों की संभावना / आवृत्ति कम हो जाती है। बेशक, यह एक बड़ी समस्या नहीं है, और सौभाग्य से यह एक ऐसी समस्या है जो वास्तव में समय के साथ कम हो जाती है।

मेरे दृष्टिकोण के साथ मुद्दा ऊपर वर्णित किया गया था: यह केवल तब होता है जब दो सत्र एक ही समय में एक ही कुंजी मान सम्मिलित करने का प्रयास करते हैं । इस संबंध में यह घटित होता है कि क्या होने की अधिक संभावना है: एक ही समय में दो अलग, फिर भी करीबी, प्रमुख मूल्यों का प्रयास किया जाता है या एक ही समय में एक ही कुंजी मूल्य का प्रयास किया जाता है? मुझे लगता है कि उत्तर आवेषण करने वाले ऐप की संरचना में निहित है, लेकिन आम तौर पर बोलते हुए मैं इसे अधिक संभावना मानूंगा कि दो अलग-अलग मूल्य जो कि एक ही श्रेणी को साझा करने के लिए होते हैं, सम्मिलित किए जा रहे हैं। लेकिन वास्तव में पता करने का एकमात्र तरीका दोनों ओपी सिस्टम पर परीक्षण करना होगा।

अगला, आइए दो परिदृश्यों पर विचार करें और प्रत्येक दृष्टिकोण उन्हें कैसे नियंत्रित करता है:

  1. अद्वितीय मुख्य मूल्यों के लिए सभी अनुरोध:

    इस स्थिति में, CATCHमेरे सुझाव में ब्लॉक को कभी दर्ज नहीं किया गया है, इसलिए कोई "मुद्दा" नहीं है (यानी 4 ट्रान लॉग प्रविष्टियां और ऐसा करने में लगने वाला समय)। लेकिन, "धारावाहिक" दृष्टिकोण में, यहां तक ​​कि सभी आवेषण अद्वितीय होने के साथ, एक ही श्रेणी में अन्य आवेषण को अवरुद्ध करने के लिए हमेशा कुछ क्षमता होगी (यद्यपि बहुत लंबे समय तक नहीं)।

  2. एक ही समय में एक ही कुंजी मूल्य के लिए अनुरोधों की उच्च आवृत्ति:

    इस मामले में - गैर-मौजूद प्रमुख मूल्यों के लिए आने वाले अनुरोधों के संदर्भ में विशिष्टता की बहुत कम डिग्री - CATCHमेरे सुझाव में ब्लॉक नियमित रूप से दर्ज किया जाएगा। इसका प्रभाव यह होगा कि प्रत्येक असफल प्रविष्टि को ऑटो-रोलबैक में लाना होगा और ट्रांजेक्शन लॉग में 4 प्रविष्टियाँ लिखनी होंगी, जो कि हर बार एक मामूली प्रदर्शन है। लेकिन समग्र ऑपरेशन कभी भी विफल नहीं होना चाहिए (कम से कम इसके कारण नहीं)।

    ("अपडेटेड" दृष्टिकोण के पिछले संस्करण के साथ एक समस्या थी जिसने इसे गतिरोध से पीड़ित होने की अनुमति दी थी। इसे updlockसंबोधित करने के लिए एक संकेत जोड़ा गया था और अब यह गतिरोध नहीं मिलता है।)लेकिन, "क्रमबद्ध" दृष्टिकोण (यहां तक ​​कि अद्यतन, अनुकूलित संस्करण) में, ऑपरेशन गतिरोध होगा। क्यों? क्योंकि serializableव्यवहार केवल INSERTउस सीमा में संचालन को रोकता है जिसे पढ़ा गया है और इसलिए लॉक किया गया है; यह SELECTउस सीमा पर परिचालन को नहीं रोकता है ।

    serializableदृष्टिकोण, इस मामले में, कोई अतिरिक्त भूमि के ऊपर है लगता है, और मैं क्या सुझाव दे रहा हूँ की तुलना में थोड़ा बेहतर प्रदर्शन कर सकती है।

प्रदर्शन के संबंध में कई / सबसे अधिक चर्चाओं के कारण, ऐसे कई कारक होने के कारण जो परिणाम को प्रभावित कर सकते हैं, वास्तव में यह समझने का एकमात्र तरीका है कि कुछ प्रदर्शन कैसे करेंगे, लक्ष्य वातावरण में इसे आज़माने के लिए जहां यह चलेगा। उस समय यह राय का विषय नहीं होगा :)।


7

अद्यतन उत्तर


@Srutzky को जवाब

अन्य, यद्यपि मामूली, इस "क्रमबद्ध लेनदेन + OUTPUT क्लॉज" के साथ समस्या यह है कि OUTPUT क्लॉज (इसके वर्तमान उपयोग में) परिणाम सेट के रूप में डेटा को वापस भेजता है। परिणामी सेट के लिए एक सरल OUTPUT पैरामीटर की तुलना में आंतरिक कर्सर को प्रबंधित करने के लिए (और DataReader ऑब्जेक्ट को प्रबंधित करने के लिए ऐप लेयर में) SQL सर्वर में (शायद दोनों पक्षों पर) अधिक ओवरहेड की आवश्यकता होती है। यह देखते हुए कि हम केवल एकल स्केलर मान के साथ काम कर रहे हैं, और यह धारणा निष्पादन की एक उच्च आवृत्ति है, कि परिणाम सेट का अतिरिक्त ओवरहेड शायद जोड़ता है।

मैं सहमत हूं, और उन्हीं कारणों से मैं विवेकपूर्ण होने पर आउटपुट मापदंडों का उपयोग करता हूं । यह मेरी गलती थी कि मेरे प्रारंभिक उत्तर पर आउटपुट पैरामीटर का उपयोग नहीं किया गया था, मैं आलसी हो रहा था।

यहां आउटपुट पैरामीटर, अतिरिक्त अनुकूलन का उपयोग करते हुए एक संशोधित प्रक्रिया है, इसके साथ next value forही @srutzky अपने उत्तर में बताते हैं :

create procedure dbo.NameLookup_getset_byName (@vName nvarchar(50), @vValueId int output) as
begin
  set nocount on;
  set xact_abort on;
  set @vValueId = null;
  if nullif(@vName,'') is null                                 
    return;                                        /* if @vName is empty, return early */
  select  @vValueId = Id                                              /* go get the Id */
    from  dbo.NameLookup
    where ItemName = @vName;
  if @vValueId is not null                                 /* if we got the id, return */
    return;
  begin try;                                  /* if it is not there, then get the lock */
    begin tran;
      select  @vValueId = Id
        from  dbo.NameLookup with (updlock, serializable) /* hold key range for @vName */
        where ItemName = @vName;
      if @@rowcount = 0                    /* if we still do not have an Id for @vName */
      begin;                                         /* get a new Id and insert @vName */
        set @vValueId = next value for dbo.IdSequence;      /* get next sequence value */
        insert into dbo.NameLookup (ItemName, Id)
          values (@vName, @vValueId);
      end;
    commit tran;
  end try
  begin catch;
    if @@trancount > 0 
      begin;
        rollback transaction;
        throw;
      end;
  end catch;
end;

अद्यतन नोट : updlockचयन के साथ शामिल इस परिदृश्य में उचित ताले हड़पने होगा। @Srutzky का धन्यवाद, जिन्होंने बताया कि यह केवल गति का उपयोग करने serializableपर गतिरोध पैदा कर सकता है select

नोट: यह मामला नहीं हो सकता है, लेकिन अगर यह संभव है कि प्रक्रिया को इसके लिए एक मूल्य के साथ बुलाया जाएगा @vValueId, set @vValueId = null;बाद में शामिल करें set xact_abort on;, अन्यथा इसे हटाया जा सकता है।


कुंजी रेंज लॉकिंग व्यवहार के @ srutzky के उदाहरणों के बारे में:

@srzzky केवल अपनी तालिका में एक मान का उपयोग करता है, और कुंजी रेंज लॉकिंग को चित्रित करने के लिए अपने परीक्षणों के लिए "अगला" / "अनन्तता" कुंजी लॉक करता है। जबकि उनके परीक्षण बताते हैं कि उन स्थितियों में क्या होता है, मेरा मानना ​​है कि जिस तरह से जानकारी serializableप्रस्तुत की गई है, वह मूल प्रश्न में प्रस्तुत किए गए अनुसार परिदृश्य का उपयोग करते समय मुठभेड़ की उम्मीद कर सकती है ।

भले ही मैं एक पूर्वाग्रह (शायद झूठा) का अनुभव करता हूं, जिस तरह से वह अपनी व्याख्या और कुंजी रेंज लॉकिंग के उदाहरण प्रस्तुत करता है, वे अभी भी सही हैं।


अधिक शोध के बाद, मैंने माइकल जे। स्वार्ट: मिथबस्टिंग: समवर्ती अपडेट / इंसर्ट सॉल्यूशंस द्वारा 2011 से एक विशेष रूप से प्रासंगिक ब्लॉग लेख पाया । इसमें, वह सटीकता और संक्षिप्तता के लिए कई तरीकों का परीक्षण करता है। विधि 4: बढ़ी हुई अलगाव + ललित ट्यूनिंग ताले सैम केसर के पोस्ट डालने या SQL सर्वर के लिए अद्यतन पैटर्न पर आधारित है , और उनकी अपेक्षाओं को पूरा करने के लिए मूल परीक्षण में एकमात्र तरीका है (बाद में शामिल हो गए merge with (holdlock))।

2016 के फरवरी में, माइकल जे। स्वार्ट ने द विन के लिए बदसूरत प्रगतिवाद पोस्ट किया । उस पोस्ट में, उन्होंने लॉकिंग को कम करने के लिए अपनी भगवा उपरिशायी प्रक्रियाओं में किए गए कुछ अतिरिक्त ट्यूनिंग को कवर किया (जिसे मैंने प्रक्रिया में शामिल किया था)।

उन परिवर्तनों को करने के बाद, माइकल खुश नहीं थे कि उनकी प्रक्रिया अधिक जटिल लग रही थी और क्रिस नामक एक सहयोगी के साथ परामर्श किया। क्रिस ने सभी मूल Mythbusters पोस्ट को पढ़ा और सभी टिप्पणियों को पढ़ा और @ gbn के TRY CATCH JFDI पैटर्न के बारे में पूछा । यह पैटर्न @ srutzky के उत्तर के समान है, और इसका समाधान है कि माइकल ने उस उदाहरण का उपयोग करके समाप्त किया।

माइकल जे स्वार्ट:

कल मैंने अपने दिमाग को सुगमता से करने का सबसे अच्छा तरीका बदल दिया था। मैं Mythbusting में कई विधियों का वर्णन करता हूं: समवर्ती अद्यतन / सम्मिलित समाधान। मेरा पसंदीदा तरीका आइसोलेशन लेवल और फाइन ट्यून लॉक को बढ़ाना है।

कम से कम मेरी प्राथमिकता तो यही थी। मैंने हाल ही में टिप्पणियों में सुझाए गए एक विधि का उपयोग करने के लिए अपना दृष्टिकोण बदल दिया। उन्होंने अपने तरीके का वर्णन "TRY CATCH JFDI पैटर्न" के रूप में किया है। आम तौर पर मैं इस तरह के समाधान से बचता हूं। अंगूठे का एक नियम है जो कहता है कि डेवलपर्स को नियंत्रण प्रवाह के लिए त्रुटियों या अपवादों को पकड़ने पर भरोसा नहीं करना चाहिए। लेकिन मैंने कल अंगूठे के उस नियम को तोड़ दिया।

वैसे, मुझे "JFDI" पैटर्न के लिए gbn का विवरण बहुत पसंद है। यह मुझे शिया लबौफ के प्रेरक वीडियो की याद दिलाता है।


मेरी राय में, दोनों समाधान व्यवहार्य हैं। हालांकि मैं अभी भी आइसोलेशन लेवल और फाइन ट्यून लॉक को बढ़ाना पसंद करता हूं, @ srutzky का जवाब भी मान्य है और आपकी विशिष्ट स्थिति में अधिक प्रदर्शन नहीं हो सकता है।

शायद भविष्य में मैं भी उसी नतीजे पर पहुंचूंगा जो माइकल जे। स्वार्ट ने किया था, लेकिन मैं अभी वहां नहीं हूं।


यह मेरी प्राथमिकता नहीं है, लेकिन यहां माइकल जे। स्टीवर्ट के @ gbn के ट्राय कैच JFDI प्रक्रिया के रूपांतर के बारे में मेरा दृष्टिकोण इस तरह दिखेगा:

create procedure dbo.NameLookup_JFDI (
    @vName nvarchar(50)
  , @vValueId int output
  ) as
begin
  set nocount on;
  set xact_abort on;
  set @vValueId = null;
  if nullif(@vName,'') is null                                 
    return;                     /* if @vName is empty, return early */
  begin try                                                 /* JFDI */
    insert into dbo.NameLookup (ItemName)
      select @vName
      where not exists (
        select 1
          from dbo.NameLookup
          where ItemName = @vName);
  end try
  begin catch        /* ignore duplicate key errors, throw the rest */
    if error_number() not in (2601, 2627) throw;
  end catch
  select  @vValueId = Id                              /* get the Id */
    from  dbo.NameLookup
    where ItemName = @vName
  end;

यदि आप मौजूदा मानों के चयन की तुलना में अधिक बार नए मान सम्मिलित कर रहे हैं, तो यह @ srutzky के संस्करण से अधिक प्रदर्शनकारी हो सकता है । अन्यथा मैं इस पर @ srutzky के संस्करण को पसंद करूंगा ।

माइकल जे स्वार्ट के पोस्ट पर प्रासंगिक परीक्षण के लिए एरॉन बर्ट्रेंड की टिप्पणियां उन्होंने इस एक्सचेंज के लिए की हैं और इस एक्सचेंज का नेतृत्व किया है। विजेता के लिए बदसूरत प्रगतिवाद पर टिप्पणी अनुभाग से अंश :

कभी-कभी, हालांकि, JFDI कुल मिलाकर खराब प्रदर्शन की ओर जाता है, इस पर निर्भर करता है कि कितने% कॉल विफल होते हैं। अपवादों को बढ़ाने के लिए पर्याप्त ओवरहेड है। मैंने इसे कुछ पोस्टों में दिखाया:

http://sqlperformance.com/2012/08/t-sql-queries/error-handling

https://www.mssqltips.com/sqlservertip/2632/checking-for-potential-constraint-violations-before-entering-sql-server-try-and-catch-logic/

आरोन बर्ट्रेंड द्वारा टिप्पणी - 11 फरवरी, 2016 @ 11:49 पूर्वाह्न

और का जवाब:

आप सही हारून हैं, और हमने इसका परीक्षण किया।

यह पता चला है कि हमारे मामले में, विफल रही कॉल का प्रतिशत 0 था (निकटतम प्रतिशत तक गोल होने पर)।

मुझे लगता है कि आप इस बिंदु को स्पष्ट करते हैं कि जितना संभव हो सके, नियम-दर-अंगूठे के आधार पर मामले-दर-मामला आधार पर चीजों का मूल्यांकन करें।

यह भी है कि हमने क्यों नहीं सख्ती से आवश्यक को जोड़ा, जहां से कुछ भी नहीं मिला।

माइकल जे। स्वार्ट द्वारा टिप्पणी - 11 फरवरी, 2016 @ 11:57 बजे


नए लिंक:


मूल उत्तर


मैं अभी भी सैम सैफ्रॉन अपर्चर अप्रोच बनाम का उपयोग करना पसंद करता हूं merge, खासकर जब एक ही पंक्ति के साथ काम कर रहा हूं ।

मैं इस तरह इस स्थिति के लिए उस तेज विधि को अनुकूलित करेगा:

declare @vName nvarchar(50) = 'Invader';
declare @vValueId int       = null;

if nullif(@vName,'') is not null /* this gets your where condition taken care of before we start doing anything */
begin tran;
  select @vValueId = Id
    from dbo.NameLookup with (serializable) 
    where ItemName = @vName;
  if @@rowcount > 0 
    begin;
      select @vValueId as id;
    end;
    else
    begin;
      insert into dbo.NameLookup (ItemName)
        output inserted.id
          values (@vName);
      end;
commit tran;

मैं आपके नामकरण के अनुरूप होगा, और जैसा serializableहै वैसा ही है, एक को holdlockचुनें और इसके उपयोग में सुसंगत रहें। मैं उपयोग करना चाहता हूं serializableक्योंकि यह वही नाम है जिसका उपयोग निर्दिष्ट करते समय किया जाता है set transaction isolation level serializable

का उपयोग करके serializableया holdlockएक रेंज लॉक को उस मूल्य के आधार पर लिया जाता है, @vNameजो किसी अन्य संचालन को प्रतीक्षा करता है यदि वे मानों का चयन करते हैं या dbo.NameLookupउस में मूल्य सम्मिलित करते हैं, जो whereखंड में मूल्य शामिल करते हैं ।

ठीक से काम करने के लिए रेंज लॉक के लिए, ItemNameस्तंभ पर एक इंडेक्स होना चाहिए जो कि उपयोग करते समय लागू होता है merge


यहाँ क्या प्रक्रिया कैसा दिखेगा है ज्यादातर निम्न त्रुटि से निपटने के लिए Erland Sommarskog के श्वेतपत्र , का उपयोग करते हुए throw। यदि throwआप अपनी त्रुटियों को नहीं बढ़ा रहे हैं, तो इसे अपनी बाकी प्रक्रियाओं के अनुरूप बदलें:

create procedure dbo.NameLookup_getset_byName (@vName nvarchar(50) ) as
begin
  set nocount on;
  set xact_abort on;
  declare @vValueId int;
  if nullif(@vName,'') is null /* if @vName is null or empty, select Id as null */
    begin
      select Id = cast(null as int);
    end 
    else                       /* else go get the Id */
    begin try;
      begin tran;
        select @vValueId = Id
          from dbo.NameLookup with (serializable) /* hold key range for @vName */
          where ItemName = @vName;
        if @@rowcount > 0      /* if we have an Id for @vName select @vValueId */
          begin;
            select @vValueId as Id; 
          end;
          else                     /* else insert @vName and output the new Id */
          begin;
            insert into dbo.NameLookup (ItemName)
              output inserted.Id
                values (@vName);
            end;
      commit tran;
    end try
    begin catch;
      if @@trancount > 0 
        begin;
          rollback transaction;
          throw;
        end;
    end catch;
  end;
go

उपरोक्त प्रक्रिया में जो चल रहा है उसे संक्षेप में प्रस्तुत करने के लिए: set nocount on; set xact_abort on;जैसे आप हमेशा करते हैं , फिर यदि परिणाम के रूप में हमारा इनपुट चर is nullया खाली है select id = cast(null as int)। यदि यह शून्य या खाली नहीं है, तो उस स्थान पर नहीं Idहोने पर हमारे वैरिएबल के लिए प्राप्त करें । यदि वहाँ है, तो इसे भेजें। यदि यह नहीं है, तो इसे डालें और उस नए को भेजें ।IdId

इस बीच, इस प्रक्रिया के लिए अन्य कॉल उसी मूल्य के लिए आईडी ढूंढने की कोशिश कर रहे हैं जब तक कि पहले लेनदेन नहीं हो जाता है और तब तक उसका चयन करें और उसे वापस करें। इस प्रक्रिया के लिए अन्य कॉल या अन्य मूल्यों की तलाश में अन्य बयान जारी रहेंगे क्योंकि यह एक तरह से नहीं है।

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

तालिका संकेत पर sql सर्वर प्रलेखनserializableholdlock से उद्धरण / :

serializable

HOLDLOCK के बराबर है। जब तक कोई लेन-देन पूरा नहीं हो जाता, तब तक साझा किए गए लॉक को जारी करने के बजाय, जब तक कि लेन-देन पूरा नहीं हो जाता, या लेन-देन पूरा नहीं हो जाता, तब तक किसी भी लेन-देन के पूरा होने तक, उन्हें साझा करने से अधिक प्रतिबंधों को साझा करता है। स्कैन एक ही शब्दार्थ के साथ किया जाता है, जो कि SERIALIZABLE अलगाव स्तर पर चल रहे लेनदेन के रूप में होता है। आइसोलेशन लेवल के बारे में अधिक जानकारी के लिए, SET TRANSACTION ISOLATION LEVEL (Transact-SQL) देखें।

लेनदेन अलगाव स्तर पर sql सर्वर प्रलेखन से उद्धरणserializable

निम्नलिखित को निर्दिष्ट करता है:

  • विवरण उन डेटा को नहीं पढ़ सकते हैं जिन्हें संशोधित किया गया है लेकिन अभी तक अन्य लेनदेन द्वारा प्रतिबद्ध नहीं हैं।

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

  • अन्य लेनदेन मुख्य मूल्यों के साथ नई पंक्तियाँ नहीं डाल सकते हैं जो वर्तमान लेनदेन में किसी भी बयान द्वारा पढ़ी गई कुंजियों की श्रेणी में आते हैं जब तक कि वर्तमान लेनदेन पूरा नहीं हो जाता।


उपरोक्त समाधान से संबंधित लिंक:

MERGEएक धब्बेदार इतिहास है, और यह सुनिश्चित करने के लिए कि यह व्यवहार कर रहा है कि कोड कैसे व्यवहार कर रहा है, यह सुनिश्चित करने के लिए चारों ओर अधिक प्रहार करने लगता है। प्रासंगिक mergeलेख:

एक आखिरी कड़ी, केंद्र लिटिल ने बनाम कीmergeinsert with left join एक मोटी तुलना की , जिसमें उन्होंने कहा कि "मैं इस पर पूरी तरह से लोड परीक्षण नहीं करता था", लेकिन यह अभी भी एक अच्छा पढ़ा है।

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