SQL सर्वर में गतिरोध के बिना एक प्रमुख तालिका तक समवर्ती पहुँच को संभालना


32

मेरे पास एक मेज है जो IDENTITYविभिन्न अन्य तालिकाओं में फ़ील्ड के विकल्प के रूप में एक विरासत अनुप्रयोग द्वारा उपयोग की जाती है ।

तालिका में प्रत्येक पंक्ति LastIDनाम के क्षेत्र के लिए अंतिम उपयोग की गई आईडी को संग्रहीत करती है IDName

कभी-कभी संग्रहीत खरीद में गतिरोध हो जाता है - मुझे विश्वास है कि मैंने एक उपयुक्त त्रुटि हैंडलर बनाया है; हालाँकि मुझे यह देखने में दिलचस्पी है कि क्या यह कार्यप्रणाली काम करती है जैसा कि मुझे लगता है कि यह करता है, या अगर मैं यहाँ गलत पेड़ को भौंक रहा हूं।

मुझे पूरा यकीन है कि बिना किसी गतिरोध के इस तालिका तक पहुंचने का एक तरीका होना चाहिए।

डेटाबेस के साथ ही कॉन्फ़िगर किया गया है READ_COMMITTED_SNAPSHOT = 1

सबसे पहले, यहाँ तालिका है:

CREATE TABLE [dbo].[tblIDs](
    [IDListID] [int] NOT NULL 
        CONSTRAINT PK_tblIDs 
        PRIMARY KEY CLUSTERED 
        IDENTITY(1,1) ,
    [IDName] [nvarchar](255) NULL,
    [LastID] [int] NULL,
);

और IDNameक्षेत्र पर गैर-अनुक्रमित सूचकांक :

CREATE NONCLUSTERED INDEX [IX_tblIDs_IDName] 
ON [dbo].[tblIDs]
(
    [IDName] ASC
) 
WITH (
    PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON
    , FILLFACTOR = 80
);

GO

कुछ नमूना डेटा:

INSERT INTO tblIDs (IDName, LastID) 
    VALUES ('SomeTestID', 1);
INSERT INTO tblIDs (IDName, LastID) 
    VALUES ('SomeOtherTestID', 1);
GO

संग्रहीत कार्यविधि तालिका में संग्रहीत मानों को अद्यतन करने के लिए उपयोग की जाती है, और अगली आईडी लौटाती है:

CREATE PROCEDURE [dbo].[GetNextID](
    @IDName nvarchar(255)
)
AS
BEGIN
    /*
        Description:    Increments and returns the LastID value from tblIDs
        for a given IDName
        Author:         Max Vernon
        Date:           2012-07-19
    */

    DECLARE @Retry int;
    DECLARE @EN int, @ES int, @ET int;
    SET @Retry = 5;
    DECLARE @NewID int;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SET NOCOUNT ON;
    WHILE @Retry > 0
    BEGIN
        BEGIN TRY
            BEGIN TRANSACTION;
            SET @NewID = COALESCE((SELECT LastID 
                FROM tblIDs 
                WHERE IDName = @IDName),0)+1;
            IF (SELECT COUNT(IDName) 
                FROM tblIDs 
                WHERE IDName = @IDName) = 0 
                    INSERT INTO tblIDs (IDName, LastID) 
                    VALUES (@IDName, @NewID)
            ELSE
                UPDATE tblIDs 
                SET LastID = @NewID 
                WHERE IDName = @IDName;
            COMMIT TRANSACTION;
            SET @Retry = -2; /* no need to retry since the operation completed */
        END TRY
        BEGIN CATCH
            IF (ERROR_NUMBER() = 1205) /* DEADLOCK */
                SET @Retry = @Retry - 1;
            ELSE
                BEGIN
                SET @Retry = -1;
                SET @EN = ERROR_NUMBER();
                SET @ES = ERROR_SEVERITY();
                SET @ET = ERROR_STATE()
                RAISERROR (@EN,@ES,@ET);
                END
            ROLLBACK TRANSACTION;
        END CATCH
    END
    IF @Retry = 0 /* must have deadlock'd 5 times. */
    BEGIN
        SET @EN = 1205;
        SET @ES = 13;
        SET @ET = 1
        RAISERROR (@EN,@ES,@ET);
    END
    ELSE
        SELECT @NewID AS NewID;
END
GO

संग्रहित खरीद का नमूना निष्पादन:

EXEC GetNextID 'SomeTestID';

NewID
2

EXEC GetNextID 'SomeTestID';

NewID
3

EXEC GetNextID 'SomeOtherTestID';

NewID
2

संपादित करें:

मैंने एक नया सूचकांक जोड़ा है, क्योंकि मौजूदा सूचकांक IX_tblIDs_Name का उपयोग SP द्वारा नहीं किया जा रहा है; मुझे लगता है कि क्वेरी प्रोसेसर क्लस्टर्ड इंडेक्स का उपयोग कर रहा है क्योंकि इसे लास्टआईडी में संग्रहीत मूल्य की आवश्यकता है। वैसे भी, यह सूचकांक वास्तविक निष्पादन योजना द्वारा उपयोग किया जाता है:

CREATE NONCLUSTERED INDEX IX_tblIDs_IDName_LastID 
ON dbo.tblIDs
(
    IDName ASC
) 
INCLUDE
(
    LastID
)
WITH (FILLFACTOR = 100
    , ONLINE=ON
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON);

EDIT # 2:

मैंने सलाह ली है कि @AaronBertrand ने दिया और इसे थोड़ा संशोधित किया। यहां सामान्य विचार अनावश्यक लॉकिंग को खत्म करने के लिए बयान को परिष्कृत करने और समग्र रूप से सपा को अधिक कुशल बनाने के लिए है।

नीचे दिया गया कोड ऊपर दिए गए कोड को निम्न से बदल देता BEGIN TRANSACTIONहै END TRANSACTION:

BEGIN TRANSACTION;
SET @NewID = COALESCE((SELECT LastID 
        FROM dbo.tblIDs 
        WHERE IDName = @IDName), 0) + 1;

IF @NewID = 1
    INSERT INTO tblIDs (IDName, LastID) 
    VALUES (@IDName, @NewID);
ELSE
    UPDATE dbo.tblIDs 
    SET LastID = @NewID 
    WHERE IDName = @IDName;

COMMIT TRANSACTION;

चूँकि हमारा कोड कभी भी इस तालिका में एक रिकॉर्ड नहीं जोड़ता है जिसमें 0 में LastIDहम यह अनुमान लगा सकते हैं कि यदि @NewID 1 है, तो इरादा सूची में एक नई आईडी संलग्न करता है, अन्यथा हम सूची में एक मौजूदा पंक्ति को अपडेट कर रहे हैं।


RCSI का समर्थन करने के लिए आपने डेटाबेस को कैसे कॉन्फ़िगर किया है वह अप्रासंगिक है। आप जान बूझकर SERIALIZABLEयहां तक बढ़ रहे हैं।
हारून बर्ट्रेंड

हां, मैं सिर्फ सभी प्रासंगिक जानकारी जोड़ना चाहता था। मुझे खुशी है कि आप पुष्टि कर रहे हैं कि यह अप्रासंगिक है!
मैक्स वर्नोन

यह बहुत आसान है कि sp_getapplock एक डेडलॉक का शिकार बन जाए, लेकिन यदि आप लेनदेन शुरू नहीं करते हैं, तो एक विशेष लॉक प्राप्त करने के लिए एक बार sp_getapplock कॉल करें, और अपने संशोधन के साथ आगे बढ़ें।
एके

1
क्या IDName अद्वितीय है? फिर " अद्वितीय गैर-अनुक्रमित सूचकांक बनाएं" को फिर से लिखें । हालाँकि यदि आपको अशक्त मूल्यों की आवश्यकता है तो सूचकांक को भी फ़िल्टर करने की आवश्यकता होगी ।
क्रोकेक

जवाबों:


15

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

गतिरोधों से पूरी तरह से बचना संभव है। मेरे पास अपने सिस्टम में कोई गतिरोध नहीं है। इसे पूरा करने के कई तरीके हैं। मैं दिखाऊंगा कि कैसे मैं गतिरोध को खत्म करने के लिए sp_getapplock का उपयोग करूंगा। मुझे पता नहीं है कि यह आपके लिए काम करेगा, क्योंकि SQL सर्वर बंद स्रोत है, इसलिए मैं स्रोत कोड नहीं देख सकता हूं, और जैसे कि मुझे नहीं पता कि क्या मैंने सभी संभावित मामलों का परीक्षण किया है।

निम्नलिखित वर्णन करता है कि मेरे लिए क्या काम करता है। YMMV।

सबसे पहले, हम एक ऐसे परिदृश्य से शुरुआत करते हैं जहाँ हमें हमेशा पर्याप्त मात्रा में गतिरोध मिलते हैं। दूसरा, हम उन्हें समाप्त करने के लिए sp_getapplock का उपयोग करेंगे। यहां सबसे महत्वपूर्ण बिंदु तनाव आपके समाधान का परीक्षण करना है। आपका समाधान अलग हो सकता है, लेकिन आपको इसे उच्च संगति के लिए उजागर करने की आवश्यकता है, जैसा कि मैं बाद में प्रदर्शित करूंगा।

आवश्यक शर्तें

आइए हम कुछ परीक्षण डेटा के साथ एक तालिका सेट करते हैं:

CREATE TABLE dbo.Numbers(n INT NOT NULL PRIMARY KEY); 
GO 

INSERT INTO dbo.Numbers 
    ( n ) 
        VALUES  ( 1 ); 
GO 
DECLARE @i INT; 
    SET @i=0; 
WHILE @i<21  
    BEGIN 
    INSERT INTO dbo.Numbers 
        ( n ) 
        SELECT n + POWER(2, @i) 
        FROM dbo.Numbers; 
    SET @i = @i + 1; 
    END;  
GO

SELECT n AS ID, n AS Key1, n AS Key2, 0 AS Counter1, 0 AS Counter2
INTO dbo.DeadlockTest FROM dbo.Numbers
GO

ALTER TABLE dbo.DeadlockTest ADD CONSTRAINT PK_DeadlockTest PRIMARY KEY(ID);
GO

CREATE INDEX DeadlockTestKey1 ON dbo.DeadlockTest(Key1);
GO

CREATE INDEX DeadlockTestKey2 ON dbo.DeadlockTest(Key2);
GO

निम्नलिखित दो प्रक्रियाएं एक गतिरोध में गले लगने की संभावना हैं:

CREATE PROCEDURE dbo.UpdateCounter1 @Key1 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
SET @Key1=@Key1-10000;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
COMMIT;
GO

CREATE PROCEDURE dbo.UpdateCounter2 @Key2 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
SET @Key2=@Key2-10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
SET @Key2=@Key2+10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
COMMIT;
GO

डेडलॉक को फिर से प्रस्तुत करना

निम्नलिखित छोरों को हर बार जब आप उन्हें चलाते हैं तो 20 से अधिक गतिरोधों को पुन: उत्पन्न करना चाहिए। यदि आप 20 से कम मिलते हैं, तो पुनरावृत्तियों की संख्या बढ़ाएं।

एक टैब में, इसे चलाएं;

DECLARE @i INT, @DeadlockCount INT;
SELECT @i=0, @DeadlockCount=0;

WHILE @i<5000 BEGIN ;
  BEGIN TRY 
    EXEC dbo.UpdateCounter1 @Key1=123456;
  END TRY
  BEGIN CATCH
    SET @DeadlockCount = @DeadlockCount + 1;
    ROLLBACK;
  END CATCH ;
  SET @i = @i + 1;
END;
SELECT 'Deadlocks caught: ', @DeadlockCount ;

किसी अन्य टैब में, यह स्क्रिप्ट चलाएँ।

DECLARE @i INT, @DeadlockCount INT;
SELECT @i=0, @DeadlockCount=0;

WHILE @i<5000 BEGIN ;
  BEGIN TRY 
    EXEC dbo.UpdateCounter2 @Key2=123456;
  END TRY
  BEGIN CATCH
    SET @DeadlockCount = @DeadlockCount + 1;
    ROLLBACK;
  END CATCH ;
  SET @i = @i + 1;
END;
SELECT 'Deadlocks caught: ', @DeadlockCount ;

सुनिश्चित करें कि आप कुछ सेकंड के भीतर दोनों शुरू करते हैं।

गतिरोध को समाप्त करने के लिए sp_getapplock का उपयोग करना

दोनों प्रक्रियाओं को बदल दें, लूप को फिर से चलाएँ, और देखें कि अब आपके पास गतिरोध नहीं हैं:

ALTER PROCEDURE dbo.UpdateCounter1 @Key1 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
SET @Key1=@Key1-10000;
UPDATE dbo.DeadlockTest SET Counter1=Counter1+1 WHERE Key1=@Key1;
COMMIT;
GO

ALTER PROCEDURE dbo.UpdateCounter2 @Key2 INT
AS
SET NOCOUNT ON ;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION ;
EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';
SET @Key2=@Key2-10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
SET @Key2=@Key2+10000;
UPDATE dbo.DeadlockTest SET Counter2=Counter2+1 WHERE Key2=@Key2;
COMMIT;
GO

गतिरोध को खत्म करने के लिए एक पंक्ति के साथ एक तालिका का उपयोग करना

Sp_getapplock को लागू करने के बजाय, हम निम्नलिखित तालिका को संशोधित कर सकते हैं:

CREATE TABLE dbo.DeadlockTestMutex(
ID INT NOT NULL,
CONSTRAINT PK_DeadlockTestMutex PRIMARY KEY(ID),
Toggle INT NOT NULL);
GO

INSERT INTO dbo.DeadlockTestMutex(ID, Toggle)
VALUES(1,0);

एक बार जब हम इस तालिका को बना और आबाद कर लेते हैं, तो हम निम्नलिखित पंक्ति को बदल सकते हैं

EXEC sp_getapplock @Resource='DeadlockTest', @LockMode='Exclusive';

इस एक के साथ, दोनों प्रक्रियाओं में:

UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;

आप तनाव परीक्षण फिर से कर सकते हैं, और अपने आप को देख सकते हैं कि हमारे पास कोई गतिरोध नहीं है।

निष्कर्ष

जैसा कि हमने देखा है, sp_getapplock का उपयोग अन्य संसाधनों तक पहुंच को अनुक्रमित करने के लिए किया जा सकता है। जैसे कि इसका उपयोग गतिरोध को खत्म करने के लिए किया जा सकता है।

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

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

सामान्य तौर पर, मुझे नहीं लगता कि यह निर्धारित करने का एक अच्छा तरीका है कि आपकी टी-एसक्यूएल गतिरोधों से सुरक्षित है या नहीं केवल निष्पादन योजना को देखकर। IMO यह निर्धारित करने का एकमात्र तरीका है कि आपके कोड में गतिरोध है या नहीं, इसे उच्च संगामिति में उजागर करना है।

गतिरोध दूर करने का सौभाग्य! हमारे पास अपने सिस्टम में बिल्कुल भी गतिरोध नहीं है, जो हमारे कार्य-जीवन के संतुलन के लिए बहुत अच्छा है।


2
+1 के रूप में sp_getapplock एक उपयोगी उपकरण है जो अच्छी तरह से ज्ञात नहीं है। यह देखते हुए कि 'भयानक गंदगी को अलग करने में समय लग सकता है, यह एक प्रक्रिया है जो गतिरोध को कम करने के लिए आसान है। लेकिन, क्या इस तरह के मामले के लिए पहली पसंद होनी चाहिए जो आसानी से समझ में आ जाए और मानक लॉकिंग तंत्र द्वारा निपटाया जा सकता है (शायद)?
मार्क स्टोरी-स्मिथ

2
@ MarkStorey-Smith यह मेरी पहली पसंद है क्योंकि मैंने शोध किया है और तनाव ने केवल एक बार इसका परीक्षण किया है, और मैं इसे किसी भी स्थिति में पुन: उपयोग कर सकता हूं - क्रमांकन पहले ही हो चुका है, इसलिए sp_getapplock के बाद होने वाली हर चीज के परिणाम पर असर नहीं पड़ता है। मानक लॉकिंग तंत्र के साथ, मैं कभी भी निश्चित नहीं हो सकता हूं - एक इंडेक्स जोड़ने या सिर्फ एक और निष्पादन योजना प्राप्त करने से गतिरोध पैदा हो सकता है जहां पहले कोई नहीं था। मुझसे पूछो कि मैं कैसे जानता हूं।
एके

मुझे लगता है कि मैं कुछ स्पष्ट याद कर रहा हूँ, लेकिन कैसे UPDATE dbo.DeadlockTestMutex SET Toggle = 1 - Toggle WHERE ID = 1;गतिरोध को रोकने का उपयोग करता है ?
डेल के

9

XLOCKआपके SELECTदृष्टिकोण या निम्नलिखित पर संकेत का उपयोग UPDATEइस प्रकार के गतिरोध के प्रति प्रतिरक्षा होना चाहिए:

DECLARE @Output TABLE ([NewId] INT);
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

BEGIN TRANSACTION;

UPDATE
    dbo.tblIDs WITH (XLOCK)
SET 
    LastID = LastID + 1
OUTPUT
    INSERTED.[LastId] INTO @Output
WHERE
    IDName = @IDName;

IF(@@ROWCOUNT = 1)
BEGIN
    SELECT @NewId = [NewId] FROM @Output;
END
ELSE
BEGIN
    SET @NewId = 1;

    INSERT dbo.tblIDs
        (IDName, LastID)
    VALUES
        (@IDName, @NewId);
END

SELECT [NewId] = @NewId ;

COMMIT TRANSACTION;

अन्य वेरिएंट के एक जोड़े के साथ वापस आ जाएगा (यदि इसे नहीं पीटा!)।


जबकि XLOCKकई कनेक्शन से अपडेट किया जा रहा से एक मौजूदा काउंटर पाएगा, तो आप एक की जरूरत नहीं होगी TABLOCKXही नया काउंटर जोड़ने से कई कनेक्शन को रोकने के लिए?
डेल के

1
@DaleBurrell नहीं, आपके पास IDName पर PK या अद्वितीय बाधा होगी।
मार्क स्टोरी-स्मिथ

7

माइक डेफ़र ने मुझे एक बहुत ही हल्के तरीके से इसे पूरा करने का एक सुंदर तरीका दिखाया:

ALTER PROCEDURE [dbo].[GetNextID](
    @IDName nvarchar(255)
)
AS
BEGIN
    /*
        Description:    Increments and returns the LastID value from tblIDs for a given IDName
        Author:         Max Vernon / Mike Defehr
        Date:           2012-07-19

    */

    DECLARE @Retry int;
    DECLARE @EN int, @ES int, @ET int;
    SET @Retry = 5;
    DECLARE @NewID int;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SET NOCOUNT ON;
    WHILE @Retry > 0
    BEGIN
        BEGIN TRY
            UPDATE dbo.tblIDs 
            SET @NewID = LastID = LastID + 1 
            WHERE IDName = @IDName;

            IF @NewID IS NULL
            BEGIN
                SET @NewID = 1;
                INSERT INTO tblIDs (IDName, LastID) VALUES (@IDName, @NewID);
            END
            SET @Retry = -2; /* no need to retry since the operation completed */
        END TRY
        BEGIN CATCH
            IF (ERROR_NUMBER() = 1205) /* DEADLOCK */
                SET @Retry = @Retry - 1;
            ELSE
                BEGIN
                SET @Retry = -1;
                SET @EN = ERROR_NUMBER();
                SET @ES = ERROR_SEVERITY();
                SET @ET = ERROR_STATE()
                RAISERROR (@EN,@ES,@ET);
                END
        END CATCH
    END
    IF @Retry = 0 /* must have deadlock'd 5 times. */
    BEGIN
        SET @EN = 1205;
        SET @ES = 13;
        SET @ET = 1
        RAISERROR (@EN,@ES,@ET);
    END
    ELSE
        SELECT @NewID AS NewID;
END
GO

(पूर्णता के लिए, यहां संग्रहित खरीद से जुड़ी तालिका है)

CREATE TABLE [dbo].[tblIDs]
(
    IDName nvarchar(255) NOT NULL,
    LastID int NULL,
    CONSTRAINT [PK_tblIDs] PRIMARY KEY CLUSTERED 
    (
        [IDName] ASC
    ) WITH 
    (
        PAD_INDEX = OFF
        , STATISTICS_NORECOMPUTE = OFF
        , IGNORE_DUP_KEY = OFF
        , ALLOW_ROW_LOCKS = ON
        , ALLOW_PAGE_LOCKS = ON
        , FILLFACTOR = 100
    ) 
);
GO

यह नवीनतम संस्करण के लिए निष्पादन योजना है:

यहाँ छवि विवरण दर्ज करें

और यह मूल संस्करण (गतिरोधी अतिसंवेदनशील) के लिए निष्पादन योजना है:

यहाँ छवि विवरण दर्ज करें

जाहिर है, नया संस्करण जीतता है!

तुलना के लिए, (XLOCK)आदि के साथ मध्यवर्ती संस्करण , निम्नलिखित योजना का उत्पादन करता है:

यहाँ छवि विवरण दर्ज करें

मैं कहता हूँ कि एक जीत है! हर किसी की मदद के लिए धन्यवाद!


2
वास्तव में काम करना चाहिए, लेकिन आप जहां यह लागू नहीं है, वहां सीरियल का उपयोग कर रहे हैं। प्रेत पंक्तियाँ यहाँ मौजूद नहीं हो सकती हैं, इसलिए उन्हें रोकने के लिए मौजूद अलगाव स्तर का उपयोग क्यों करें? इसके अलावा, यदि कोई आपकी प्रक्रिया को दूसरे से या एक कनेक्शन से कहता है जहां एक बाहरी लेनदेन शुरू किया गया था, तो वे जो भी कार्रवाई शुरू करते हैं, वह SERIZIZABLE पर आगे बढ़ेगा। जिससे गड़बड़ हो सकती है।
मार्क स्टोरी-स्मिथ

2
SERIALIZABLEप्रेत को रोकने के लिए मौजूद नहीं है। यह अनुक्रमिक अलगाव अलगाव शब्दार्थ प्रदान करने के लिए मौजूद है , अर्थात डेटाबेस पर समान रूप से लगातार प्रभाव जैसे कि लेनदेन में कुछ अनिर्दिष्ट क्रम में क्रमिक रूप से निष्पादित किया गया था।
पॉल व्हाइट GoFundMonica कहते

6

मार्क स्टोरी-स्मिथ की गड़गड़ाहट को चोरी करने के लिए नहीं, लेकिन वह अपने पद के साथ कुछ ऊपर है (जो संयोग से सबसे अधिक अपवित्र प्राप्त हुआ है)। मैक्स ने जो सलाह दी, वह "UPDATE सेट @variable = column = column + value" निर्माण के इर्द-गिर्द केंद्रित थी, जो मुझे वास्तव में अच्छी लगती है, लेकिन मुझे लगता है कि यह अनिर्दिष्ट हो सकती है (हालांकि इसका समर्थन करना होगा, हालांकि यह विशेष रूप से टीसीपी के लिए है। मानक)।

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

ALTER PROC [dbo].[GetNextID]
  @IDName nvarchar(255)
  AS
BEGIN
SET NOCOUNT ON;

DECLARE @Output TABLE ([NewID] INT);

UPDATE dbo.tblIDs SET LastID = LastID + 1
OUTPUT inserted.[LastId] INTO @Output
WHERE IDName = @IDName;

IF(@@ROWCOUNT = 1)
    SELECT [NewID] FROM @Output;
ELSE
    INSERT dbo.tblIDs (IDName, LastID)
    OUTPUT INSERTED.LastID AS [NewID]
    VALUES (@IDName,1);
END

3
सहमत इस गतिरोध के लिए प्रतिरक्षा होनी चाहिए, लेकिन यह डालने पर दौड़ की स्थिति के लिए प्रवण होता है, यदि आप लेनदेन को छोड़ देते हैं।
मार्क स्टोरी-स्मिथ

4

मैंने इसे बदलकर पिछले साल एक सिस्टम में एक समान गतिरोध तय किया:

IF (SELECT COUNT(IDName) FROM tblIDs WHERE IDName = @IDName) = 0 
  INSERT INTO tblIDs (IDName, LastID) VALUES (@IDName, @NewID)
ELSE
  UPDATE tblIDs SET LastID = @NewID WHERE IDName = @IDName;

इसके लिए:

UPDATE tblIDs SET LastID = @NewID WHERE IDName = @IDName;
IF @@ROWCOUNT = 0
BEGIN
  INSERT ...
END

सामान्य तौर पर, COUNTउपस्थिति या अनुपस्थिति निर्धारित करने के लिए एक का चयन करना काफी बेकार है। इस मामले में यह या तो 0 या 1 है, क्योंकि यह नहीं है यह बहुत काम है, लेकिन (क) कि आदत अन्य मामलों में खून बहाना कर सकते हैं जहां यह की तरह होगा ज्यादा महंगे हो (उन मामलों में, का उपयोग IF NOT EXISTSकरने के बजाय IF COUNT() = 0), और (ख) अतिरिक्त स्कैन पूरी तरह से अनावश्यक है। UPDATEप्रदर्शन मूलतः एक ही जांच।

इसके अलावा, यह मेरे लिए एक गंभीर कोड गंध जैसा दिखता है:

SET @NewID = COALESCE((SELECT LastID FROM tblIDs WHERE IDName = @IDName),0)+1;

यहाँ क्या बात है? क्यों न केवल एक पहचान कॉलम का उपयोग किया जाए या उस अनुक्रम ROW_NUMBER()को क्वेरी समय पर उपयोग किया जाए?


हमारे पास उपयोग की जाने वाली अधिकांश सारणियां IDENTITY। यह तालिका एमएस एक्सेस में लिखे गए कुछ विरासत कोड का समर्थन करती है जो कि रेट्रोफिट में शामिल होगी। SET @NewID=लाइन बस (लेकिन आप पहले से ही जानते हैं कि) दी गई ID के लिए तालिका में संग्रहीत मूल्य बढ़ा देता है। क्या आप इसका विस्तार कर सकते हैं कि मैं कैसे उपयोग कर सकता हूं ROW_NUMBER()?
मैक्स वर्नोन

@MaxVernon यह जाने बिना कि LastIDवास्तव में आपके मॉडल का क्या मतलब है। इसका उद्देश्य क्या है? नाम बिल्कुल स्व-व्याख्यात्मक नहीं है। एक्सेस इसका उपयोग कैसे करता है?
हारून बर्ट्रेंड

एक्सेस में एक फ़ंक्शन किसी भी दी गई तालिका में एक पंक्ति जोड़ना चाहता है जिसमें कोई पहचान नहीं है। पहले एक्सेस कॉल GetNextID('WhatevertheIDFieldIsCalled')करने के लिए अगली आईडी प्राप्त करने के लिए उपयोग करता है, फिर इसे नई पंक्ति में सम्मिलित करता है और इसके साथ ही डेटा की भी आवश्यकता होती है।
मैक्स वर्नोन

मैं आपके बदलाव को लागू करूंगा। "कम अधिक" का एक शुद्ध मामला है!
मैक्स वर्नोन

1
आपका निश्चित गतिरोध फिर से उभर सकता है। आपका दूसरा पैटर्न भी कमजोर है: sqlblog.com/blogs/alexander_kuznetsov/archive/2010/01/12/… गतिरोध को खत्म करने के लिए मैं sp_getapplock का उपयोग करूंगा। सैकड़ों उपयोगकर्ताओं के साथ मिश्रित लोड सिस्टम में गतिरोध नहीं हो सकता है।
एके
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.