다음 stackoverflow 질문에 답변하려고했습니다.
다소 순진한 답변을 게시 한 후, 나는 입에 돈을 넣고 실제로 제안하는 시나리오를 테스트 하여 거친 거위 추적에서 OP를 보내지 않았 음을 확인했습니다. 글쎄, 그것은 내가 생각했던 것보다 훨씬 더 힘들다는 것이 밝혀졌습니다 (아무도 놀라지 않을 것입니다).
내가 시도하고 생각한 것은 다음과 같습니다.
먼저 파생 테이블 내부에서 ORDER BY를 사용하여 TOP 1 UPDATE를 시도했습니다
ROWLOCK, READPAST
. 교착 상태가 발생하고 항목이 잘못 처리되었습니다. 동일한 행을 두 번 이상 처리해야하는 오류를 제외하고 가능한 한 FIFO에 가까워 야합니다.그때의 다양한 조합을 사용하여 변수에 원하는 다음 QueueID을 선택 했어요
READPAST
,UPDLOCK
,HOLDLOCK
, 그리고ROWLOCK
독점적으로 해당 세션에 의해 업데이트에 대한 행을 유지합니다. 내가 시도한 모든 변형은 이전과 동일한 문제로 인해 문제가 발생했습니다READPAST
.READ COMMITTED 또는 REPEATABLE READ 격리 수준에서만 READPAST 잠금을 지정할 수 있습니다.
이 때문에 혼동되었다 된 READ 커밋. 나는 전에 이것에 부딪쳤다.
이 질문을 쓰기 시작한 후 Remus Rusani는이 질문에 대한 새로운 답변을 게시했습니다. 나는 그의 링크 된 기사를 읽고 그가 파괴적인 읽기를 사용하고 있음을 알았다. 왜냐하면 그의 답변에서 "웹 호출 기간 동안 잠금을 유지하는 것은 현실적으로 불가능하다"고 말했다. 업데이트 또는 삭제를 수행하기 위해 잠금이 필요한 핫스팟 및 페이지에 관한 그의 기사를 읽은 후, 내가 찾고있는 것을 수행하기 위해 올바른 잠금을 수행 할 수는 있지만 확장 할 수 없으며 대규모 동시성을 처리하지 않습니다.
지금은 어디로 가야할지 모르겠습니다. 행이 처리되는 동안 잠금을 유지 관리 할 수 없다는 것이 사실입니까 (높은 tps 또는 대규모 동시성을 지원하지 않더라도)? 내가 무엇을 놓치고 있습니까?
나보다 똑똑한 사람들과 나보다 더 경험 많은 사람들이 도울 수 있기를 바랍니다. 아래는 제가 사용하고있는 테스트 스크립트입니다. TOP 1 UPDATE 방법으로 다시 전환되었지만 다른 방법을 탐색하여 주석을 달았습니다.
이들 각각을 별도의 세션에 붙여 넣고 세션 1을 실행 한 다음 다른 모든 세션을 빠르게 수행하십시오. 약 50 초 후에 테스트가 종료됩니다. 각 세션의 메시지를보고 어떤 작업을 수행했는지 (또는 실패한 방법)를 확인하십시오. 첫 번째 세션에는 잠금 및 현재 처리중인 큐 항목을 자세히 설명하는 스냅 샷이있는 행 세트가 표시됩니다. 때로는 작동하지만 다른 시간에는 전혀 작동하지 않습니다.
세션 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)
작동해야하므로 이론적으로 ... 그리고 전에 일했다! 왜 지금 작동하지 않는지 잘 모르겠지만 아마도 다른 학습 경험 일 것입니다.