ฉันพยายามตอบคำถาม stackoverflow ต่อไปนี้:
หลังจากโพสต์คำตอบที่ไร้เดียงสาฉันคิดว่าฉันจะเอาเงินของฉันไปที่ปากของฉันและทดสอบสถานการณ์จริง ๆที่ฉันแนะนำเพื่อให้แน่ใจว่าฉันไม่ได้ส่ง OP ออกไปในการไล่ล่าห่านป่า มันกลายเป็นว่าหนักกว่าที่ฉันคิดไว้มาก (ไม่แปลกใจสำหรับทุกคนฉันแน่ใจ)
นี่คือสิ่งที่ฉันได้ลองและคิดเกี่ยวกับ:
ครั้งแรกที่ฉันพยายาม TOP 1 UPDATE กับ ORDER BY
ROWLOCK, READPAST
ภายในตารางมาใช้ สิ่งนี้ทำให้เกิดการหยุดชะงักและยังประมวลผลรายการที่ผิดปกติ จะต้องใกล้เคียงกับ FIFO เท่าที่จะทำได้ยกเว้นข้อผิดพลาดที่ต้องพยายามประมวลผลแถวเดียวกันมากกว่าหนึ่งครั้งฉันก็พยายามเลือก QueueID ถัดไปต้องการลงในตัวแปรโดยใช้ชุดต่างๆของ
READPAST
,UPDLOCK
,HOLDLOCK
และROWLOCK
เพื่อรักษาเฉพาะแถวสำหรับการปรับปรุงโดยเซสชั่นที่ รูปแบบทั้งหมดที่ฉันลองใช้นั้นประสบปัญหาเดียวกันกับที่เคยมีมาก่อนและสำหรับการรวมกันบางอย่างกับREADPAST
:คุณสามารถระบุล็อค READPAST ในระดับการแยก READ COMMITTED หรือ REPEATABLE READ
สิ่งนี้ทำให้เกิดความสับสนเพราะเป็นข้อผูกมัดที่ต้องอ่าน ฉันเคยพบเจอสิ่งนี้มาก่อนและมันน่าหงุดหงิด
ตั้งแต่ฉันเริ่มเขียนคำถามนี้ Remus Rusani โพสต์คำตอบใหม่สำหรับคำถาม ฉันอ่านบทความที่เชื่อมโยงของเขาและเห็นว่าเขากำลังใช้การอ่านที่ทำลายล้างเนื่องจากเขากล่าวในคำตอบของเขาว่า "เป็นไปไม่ได้ที่จะล็อกไว้ในระหว่างการโทรผ่านเว็บ" หลังจากอ่านสิ่งที่บทความของเขาพูดเกี่ยวกับฮอตสปอตและหน้าเว็บที่ต้องการการล็อกเพื่อทำการอัปเดตหรือลบฉันกลัวว่าแม้ว่าฉันจะสามารถใช้งานการล็อกที่ถูกต้องเพื่อทำสิ่งที่ฉันกำลังมองหามันจะไม่สามารถปรับขนาดได้ ไม่จัดการพร้อมกันมาก
ตอนนี้ฉันไม่แน่ใจว่าจะไปที่ไหน เป็นความจริงหรือไม่ว่าการดูแลรักษาล็อกในขณะที่แถวไม่สามารถดำเนินการได้ ฉันกำลังคิดถึงอะไร
ด้วยความหวังว่าผู้คนฉลาดกว่าฉันและผู้คนที่มีประสบการณ์มากกว่าที่ฉันสามารถช่วยได้ด้านล่างคือสคริปต์ทดสอบที่ฉันใช้ มันถูกเปลี่ยนกลับไปเป็นวิธีการอัปเดต TOP 1 แต่ฉันออกจากวิธีอื่นในความเห็นในกรณีที่คุณต้องการสำรวจด้วย
วางแต่ละรายการเหล่านี้ลงในเซสชันแยกต่างหากเรียกใช้เซสชัน 1 จากนั้นแยกรายการอื่น ๆ ทั้งหมดอย่างรวดเร็ว ในประมาณ 50 วินาทีการทดสอบจะสิ้นสุด ดูข้อความจากแต่ละเซสชันเพื่อดูว่ามันทำงานอย่างไร (หรือความล้มเหลว) เซสชั่นแรกจะแสดงชุดของ rowset ที่มีสแนปชอตหนึ่งครั้งที่มีรายละเอียดการล็อคที่มีอยู่และรายการคิวที่กำลังดำเนินการ มันใช้งานได้บางครั้งและเวลาอื่นไม่ทำงานเลย
เซสชั่น 1
/* Session 1: Setup and control - Run this session first, then immediately run all other sessions */
IF Object_ID('dbo.Queue', 'U') IS NULL
CREATE TABLE dbo.Queue (
QueueID int identity(1,1) NOT NULL,
StatusID int NOT NULL,
QueuedDate datetime CONSTRAINT DF_Queue_QueuedDate DEFAULT (GetDate()),
CONSTRAINT PK_Queue PRIMARY KEY CLUSTERED (QueuedDate, QueueID)
);
IF Object_ID('dbo.QueueHistory', 'U') IS NULL
CREATE TABLE dbo.QueueHistory (
HistoryDate datetime NOT NULL,
QueueID int NOT NULL
);
IF Object_ID('dbo.LockHistory', 'U') IS NULL
CREATE TABLE dbo.LockHistory (
HistoryDate datetime NOT NULL,
ResourceType varchar(100),
RequestMode varchar(100),
RequestStatus varchar(100),
ResourceDescription varchar(200),
ResourceAssociatedEntityID varchar(200)
);
IF Object_ID('dbo.StartTime', 'U') IS NULL
CREATE TABLE dbo.StartTime (
StartTime datetime NOT NULL
);
SET NOCOUNT ON;
IF (SELECT Count(*) FROM dbo.Queue) < 10000 BEGIN
TRUNCATE TABLE dbo.Queue;
WITH A (N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
B (N) AS (SELECT 1 FROM A Z, A I, A P),
C (N) AS (SELECT Row_Number() OVER (ORDER BY (SELECT 1)) FROM B O, B W)
INSERT dbo.Queue (StatusID, QueuedDate)
SELECT 1, DateAdd(millisecond, C.N * 3, GetDate() - '00:05:00')
FROM C
WHERE C.N <= 10000;
END;
TRUNCATE TABLE dbo.StartTime;
INSERT dbo.StartTime SELECT GetDate() + '00:00:15'; -- or however long it takes you to go run the other sessions
GO
TRUNCATE TABLE dbo.QueueHistory;
SET NOCOUNT ON;
DECLARE
@Time varchar(8),
@Now datetime;
SELECT @Time = Convert(varchar(8), StartTime, 114)
FROM dbo.StartTime;
WAITFOR TIME @Time;
DECLARE @i int,
@QueueID int;
SET @i = 1;
WHILE @i <= 33 BEGIN
SET @Now = GetDate();
INSERT dbo.QueueHistory
SELECT
@Now,
QueueID
FROM
dbo.Queue Q WITH (NOLOCK)
WHERE
Q.StatusID <> 1;
INSERT dbo.LockHistory
SELECT
@Now,
L.resource_type,
L.request_mode,
L.request_status,
L.resource_description,
L.resource_associated_entity_id
FROM
sys.dm_tran_current_transaction T
INNER JOIN sys.dm_tran_locks L
ON L.request_owner_id = T.transaction_id;
WAITFOR DELAY '00:00:01';
SET @i = @i + 1;
END;
WITH Cols AS (
SELECT *, Row_Number() OVER (PARTITION BY HistoryDate ORDER BY QueueID) Col
FROM dbo.QueueHistory
), P AS (
SELECT *
FROM
Cols
PIVOT (Max(QueueID) FOR Col IN ([1], [2], [3], [4], [5], [6], [7], [8])) P
)
SELECT L.*, P.[1], P.[2], P.[3], P.[4], P.[5], P.[6], P.[7], P.[8]
FROM
dbo.LockHistory L
FULL JOIN P
ON L.HistoryDate = P.HistoryDate
/* Clean up afterward
DROP TABLE dbo.StartTime;
DROP TABLE dbo.LockHistory;
DROP TABLE dbo.QueueHistory;
DROP TABLE dbo.Queue;
*/
เซสชั่น 2
/* Session 2: Simulate an application instance holding a row locked for a long period, and eventually abandoning it. */
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE
@QueueID int,
@Time varchar(8);
SELECT @Time = Convert(varchar(8), StartTime + '0:00:01', 114)
FROM dbo.StartTime;
WAITFOR TIME @Time;
BEGIN TRAN;
--SET @QueueID = (
-- SELECT TOP 1 QueueID
-- FROM dbo.Queue WITH (READPAST, UPDLOCK)
-- WHERE StatusID = 1 -- ready
-- ORDER BY QueuedDate, QueueID
--);
--UPDATE dbo.Queue
--SET StatusID = 2 -- in process
----OUTPUT Inserted.*
--WHERE QueueID = @QueueID;
SET @QueueID = NULL;
UPDATE Q
SET Q.StatusID = 1, @QueueID = Q.QueueID
FROM (
SELECT TOP 1 *
FROM dbo.Queue WITH (ROWLOCK, READPAST)
WHERE StatusID = 1
ORDER BY QueuedDate, QueueID
) Q
PRINT @QueueID;
WAITFOR DELAY '00:00:20'; -- Release it partway through the test
ROLLBACK TRAN; -- Simulate client disconnecting
เซสชั่น 3
/* Session 3: Run a near-continuous series of "failed" queue processing. */
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET XACT_ABORT ON;
SET NOCOUNT ON;
DECLARE
@QueueID int,
@EndDate datetime,
@NextDate datetime,
@Time varchar(8);
SELECT
@EndDate = StartTime + '0:00:33',
@Time = Convert(varchar(8), StartTime, 114)
FROM dbo.StartTime;
WAITFOR TIME @Time;
WHILE GetDate() < @EndDate BEGIN
BEGIN TRAN;
--SET @QueueID = (
-- SELECT TOP 1 QueueID
-- FROM dbo.Queue WITH (READPAST, UPDLOCK)
-- WHERE StatusID = 1 -- ready
-- ORDER BY QueuedDate, QueueID
--);
--UPDATE dbo.Queue
--SET StatusID = 2 -- in process
----OUTPUT Inserted.*
--WHERE QueueID = @QueueID;
SET @QueueID = NULL;
UPDATE Q
SET Q.StatusID = 1, @QueueID = Q.QueueID
FROM (
SELECT TOP 1 *
FROM dbo.Queue WITH (ROWLOCK, READPAST)
WHERE StatusID = 1
ORDER BY QueuedDate, QueueID
) Q
PRINT @QueueID;
SET @NextDate = GetDate() + '00:00:00.015';
WHILE GetDate() < @NextDate SET NOCOUNT ON;
ROLLBACK TRAN;
END
เซสชันที่ 4 ขึ้นไป - มากเท่าที่คุณต้องการ
/* Session 4: "Process" the queue normally, one every second for 30 seconds. */
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET XACT_ABORT ON;
SET NOCOUNT ON;
DECLARE @Time varchar(8);
SELECT @Time = Convert(varchar(8), StartTime, 114)
FROM dbo.StartTime;
WAITFOR TIME @Time;
DECLARE @i int,
@QueueID int;
SET @i = 1;
WHILE @i <= 30 BEGIN
BEGIN TRAN;
--SET @QueueID = (
-- SELECT TOP 1 QueueID
-- FROM dbo.Queue WITH (READPAST, UPDLOCK)
-- WHERE StatusID = 1 -- ready
-- ORDER BY QueuedDate, QueueID
--);
--UPDATE dbo.Queue
--SET StatusID = 2 -- in process
--WHERE QueueID = @QueueID;
SET @QueueID = NULL;
UPDATE Q
SET Q.StatusID = 1, @QueueID = Q.QueueID
FROM (
SELECT TOP 1 *
FROM dbo.Queue WITH (ROWLOCK, READPAST)
WHERE StatusID = 1
ORDER BY QueuedDate, QueueID
) Q
PRINT @QueueID;
WAITFOR DELAY '00:00:01'
SET @i = @i + 1;
DELETE dbo.Queue
WHERE QueueID = @QueueID;
COMMIT TRAN;
END
READPAST, UPDLOCK, ROWLOCK
สคริปต์ของฉันสำหรับเก็บข้อมูลลงในตาราง QueueHistory ไม่ได้ทำอะไรเลย ฉันสงสัยว่าเป็นเพราะ StatusID ไม่ได้มุ่งมั่นหรือไม่ มันใช้ในWITH (NOLOCK)
ทางทฤษฎีควรทำงาน ... และมันทำงานได้ก่อน! ฉันไม่แน่ใจว่าทำไมมันไม่ทำงานในตอนนี้ แต่อาจเป็นประสบการณ์การเรียนรู้อื่น