T-SQL هو استعلام فرعي لتقييد التحديث الذري مع التحديث؟

StackOverflow https://stackoverflow.com/questions/4097126

سؤال

لقد حصلت على تطبيق قائمة انتظار بسيط في MS SQL Server 2008 R2. ها هي جوهر قائمة الانتظار:

CREATE TABLE ToBeProcessed 
(
    Id BIGINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
    [Priority] INT DEFAULT(100) NOT NULL,
    IsBeingProcessed BIT default (0) NOT NULL,
    SomeData nvarchar(MAX) NOT null
)

أرغب في تحديد أهم صفوف N التي طلبتها الأولوية والمعرف الذي يكون فيه IsbeingProcorted خاطئًا وتحديث تلك الصفوف ليقول إنها تتم معالجتها. اعتقدت أنني سأستخدم مزيجًا من التحديث والأعلى والإخراج والطلب من خلال ولكن للأسف لا يمكنك استخدام TOP والطلب في عبارة تحديث.

لذلك قمت بإنشاء جملة لتقييد التحديث وأن الاستعلام الفرعي يقوم بالترتيب (انظر أدناه). سؤالي هو ، هل هذا البيان بأكمله ذري ، أم أحتاج إلى لفه في معاملة؟

DECLARE @numberToProcess INT = 2

CREATE TABLE #IdsToProcess
(
    Id BIGINT NOT null
)

UPDATE 
    ToBeProcessed
SET
    ToBeProcessed.IsBeingProcessed = 1
OUTPUT 
    INSERTED.Id 
INTO
    #IdsToProcess   
WHERE
    ToBeProcessed.Id IN 
    (
        SELECT TOP(@numberToProcess) 
            ToBeProcessed.Id 
        FROM 
            ToBeProcessed 
        WHERE
            ToBeProcessed.IsBeingProcessed = 0
        ORDER BY 
            ToBeProcessed.Id, 
            ToBeProcessed.Priority DESC)

SELECT 
    *
FROM 
    #IdsToProcess

DROP TABLE #IdsToProcess

إليك بعض SQL لإدخال بعض الصفوف الوهمية:

INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
هل كانت مفيدة؟

المحلول

إذا فهمت الدافع وراء السؤال الذي تريده لتجنب احتمال أن يمكن للمعاملتين المتزامنين تنفيذ الاستعلام الفرعي للحصول على أفضل صفوف N للمعالجة ثم المضي قدماً في تحديث الصفوف نفسها؟

في هذه الحالة كنت أستخدم هذا النهج.

;WITH cte As
(
SELECT TOP(@numberToProcess) 
            *
        FROM 
            ToBeProcessed WITH(UPDLOCK,ROWLOCK,READPAST) 
        WHERE
            ToBeProcessed.IsBeingProcessed = 0
        ORDER BY 
            ToBeProcessed.Id, 
            ToBeProcessed.Priority DESC
)            
UPDATE 
    cte
SET
    IsBeingProcessed = 1
OUTPUT 
    INSERTED.Id 
INTO
    #IdsToProcess  

كنت غير متأكدة بعض الشيء ما إذا كان SQL Server سيأخذ U يقفل عند معالجة الإصدار الخاص بك مع الاستعلام الفرعي وبالتالي منع معاملتين متزامنين من قراءة نفس TOP N الصفوف. هذا لا يبدو أن هذا هو الحال.

جدول الاختبار

CREATE TABLE JobsToProcess
(
priority INT IDENTITY(1,1),
isprocessed BIT ,
number INT
)

INSERT INTO JobsToProcess
SELECT TOP (1000000) 0,0
FROM master..spt_values v1, master..spt_values v2

اختبار البرنامج النصي (تشغيل في 2 جلسات SSMS متزامنة)

BEGIN TRY
DECLARE @FinishedMessage VARBINARY (128) = CAST('TestFinished' AS  VARBINARY (128))
DECLARE @SynchMessage VARBINARY (128) = CAST('TestSynchronising' AS  VARBINARY (128))
SET CONTEXT_INFO @SynchMessage

DECLARE @OtherSpid int

WHILE(@OtherSpid IS NULL)
SELECT @OtherSpid=spid 
FROM sys.sysprocesses 
WHERE context_info=@SynchMessage and spid<>@@SPID

SELECT @OtherSpid


DECLARE @increment INT = @@spid
DECLARE @number INT = @increment

WHILE (@number = @increment AND NOT EXISTS(SELECT * FROM sys.sysprocesses WHERE context_info=@FinishedMessage))
UPDATE JobsToProcess 
SET @number=number +=@increment,isprocessed=1
WHERE priority = (SELECT TOP 1 priority 
                   FROM JobsToProcess 
                   WHERE isprocessed=0 
                   ORDER BY priority DESC)

SELECT * 
FROM JobsToProcess 
WHERE number not in (0,@OtherSpid,@@spid)
SET CONTEXT_INFO @FinishedMessage
END TRY
BEGIN CATCH
SET CONTEXT_INFO @FinishedMessage
SELECT ERROR_MESSAGE(), ERROR_NUMBER()
END CATCH

يتوقف التنفيذ على الفور تقريبًا مع تحديث كلتا المعاملات المتزامنة نفس الصف S الأقفال التي تم التقاطها أثناء تحديد TOP 1 priority يجب إطلاق سراحها قبل ذلك U قفل ثم تتقدم المعاملات 2 للحصول على الصف U و X قفل في التسلسل.

Heap

إذا تمت إضافة CI ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority) ثم يحدث Deadlock على الفور تقريبًا بدلاً من ذلك في هذه الحالة S لا يتم إطلاق القفل ، معاملة واحدة أكاتير أ U قفل على الصف وينتظر لتحويله إلى X القفل والمعاملة الأخرى لا تزال تنتظر تحويلها S قفل إلى U قفل.

Clustered Index

إذا تم تغيير الاستعلام أعلاه للاستخدام MIN عوضا عن TOP

WHERE priority = (SELECT MIN(priority)
                   FROM JobsToProcess 
                   WHERE isprocessed=0 
                   )

ثم يتمكن SQL Server من التخلص تمامًا من الاستعلام الفرعي من الخطة ويأخذ U أقفال على طول الطريق.

enter image description here

نصائح أخرى

كل بيان T-SQL فردي هو ، وفقًا لجميع تجربتي وجميع المستندات التي قرأتها على الإطلاق ، من المفترض أن تكون ذرية. ما لديك هناك عبارة واحدة من T-SQL ، يجب أن يكون ERGO ذريًا ولن يتطلب بيانات معاملة واضحة. لقد استخدمت هذا النوع الدقيق من المنطق عدة مرات ، ولم أواجه مشكلة في ذلك. إنني أتطلع إلى رؤية ما إذا كان أي شخص بمثابة رأي بديل قابل للدعم.

بالمناسبة ، انظر إلى وظائف الترتيب ، وتحديدا ROW_Number () ، لاسترداد عدد مجموعة من العناصر. ربما يكون بناء الجملة محرجًا ، ولكنه بشكل عام أدوات مرنة وقوية. (هناك حول أسئلة وأجوبة مكدس بازليون تتمثل في مناقشتها.)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top