다음 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)작동해야하므로 이론적으로 ... 그리고 전에 일했다! 왜 지금 작동하지 않는지 잘 모르겠지만 아마도 다른 학습 경험 일 것입니다.