T-SQL هو استعلام فرعي لتقييد التحديث الذري مع التحديث؟
-
28-09-2019 - |
سؤال
لقد حصلت على تطبيق قائمة انتظار بسيط في 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
قفل في التسلسل.
إذا تمت إضافة CI ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority)
ثم يحدث Deadlock على الفور تقريبًا بدلاً من ذلك في هذه الحالة S
لا يتم إطلاق القفل ، معاملة واحدة أكاتير أ U
قفل على الصف وينتظر لتحويله إلى X
القفل والمعاملة الأخرى لا تزال تنتظر تحويلها S
قفل إلى U
قفل.
إذا تم تغيير الاستعلام أعلاه للاستخدام MIN
عوضا عن TOP
WHERE priority = (SELECT MIN(priority)
FROM JobsToProcess
WHERE isprocessed=0
)
ثم يتمكن SQL Server من التخلص تمامًا من الاستعلام الفرعي من الخطة ويأخذ U
أقفال على طول الطريق.
نصائح أخرى
كل بيان T-SQL فردي هو ، وفقًا لجميع تجربتي وجميع المستندات التي قرأتها على الإطلاق ، من المفترض أن تكون ذرية. ما لديك هناك عبارة واحدة من T-SQL ، يجب أن يكون ERGO ذريًا ولن يتطلب بيانات معاملة واضحة. لقد استخدمت هذا النوع الدقيق من المنطق عدة مرات ، ولم أواجه مشكلة في ذلك. إنني أتطلع إلى رؤية ما إذا كان أي شخص بمثابة رأي بديل قابل للدعم.
بالمناسبة ، انظر إلى وظائف الترتيب ، وتحديدا ROW_Number () ، لاسترداد عدد مجموعة من العناصر. ربما يكون بناء الجملة محرجًا ، ولكنه بشكل عام أدوات مرنة وقوية. (هناك حول أسئلة وأجوبة مكدس بازليون تتمثل في مناقشتها.)