Aşağıdaki stackoverflow sorusuna cevap vermeye çalışıyordum:
Biraz naif bir cevap gönderdikten sonra, paramı ağzımın olduğu yere koyacağımı ve aslında önerdiğim senaryoyu test edeceğimi düşündüm . Düşündüğümden çok daha zor olduğu ortaya çıktı (orada kimseye sürpriz yok, eminim).
İşte denedim ve düşündüm:
İlk kullanarak kullanarak türetilmiş bir tablo içinde ORDER BY ile TOP 1 GÜNCELLEME denedim
ROWLOCK, READPAST
. Bu, kilitlenmelere yol açtı ve ayrıca öğeleri işledi. Aynı satırı bir kereden fazla işlemeye teşebbüs gerektiren hataları engellemek için mümkün olduğunca FIFO'ya yakın olmalıdır.Sonra çeşitli kombinasyonlarını kullanarak bir değişken içine istenen sonraki QueueID seçerek çalıştı
READPAST
,UPDLOCK
,HOLDLOCK
, veROWLOCK
münhasıran bu oturumda tarafından güncellenmesi satır korumak için. Denediğim tüm varyasyonlar, daha önce olduğu gibi aynı sorunlardan ve bazı kombinasyonlar içinREADPAST
şikayetçi oldu:READPAST kilidini yalnızca READ COMMITTED veya REPEATABLE READ yalıtım seviyelerinde belirleyebilirsiniz.
Çünkü bu kafa karıştırıcı oldu OKU İŞLENEN. Bunu daha önce de gördüm ve sinir bozucu.
Bu soruyu yazmaya başladığımdan beri, Remus Rusani soruya yeni bir cevap gönderdi. Bağlantılı makalesini okudum ve yıkıcı okumalar kullandığını gördüm, çünkü cevabında "web çağrıları boyunca kilitlere tutunmanın gerçekçi bir şekilde mümkün olmadığını" söyledi. Sıcak noktalar ve herhangi bir güncelleme veya silme yapmak için kilitleme gerektiren sayfalarla ilgili makalesini okuduktan sonra, aradığım şeyi yapmak için doğru kilitleri çalışabilsem bile, ölçeklenebilir olmayacağını ve büyük eşzamanlılığı ele almaz.
Şu anda nereye gideceğimi bilmiyorum. Satır işlenirken kilitleri korumaya ulaşılamadığı doğru mu (yüksek tps veya büyük eşzamanlılığı desteklemese bile)? Neyi kaçırıyorum?
Benden daha akıllı ve benden daha deneyimli insanların yardım edebileceği ümidiyle, aşağıda kullandığım test senaryosu. TOP 1 UPDATE yöntemine geri döndü, ancak ben de keşfetmek istiyorsanız, diğer yöntemi bıraktım, yorum yaptım.
Bunların her birini ayrı bir oturuma yapıştırın, 1. oturumu çalıştırın, ardından diğerlerini hızlı bir şekilde çalıştırın. Yaklaşık 50 saniye içinde test sona erecektir. Ne işe yaradığını (veya nasıl başarısız olduğunu) görmek için her oturumdaki Mesajlara bakın. İlk oturumda, mevcut kilitlerin ve işlenmekte olan kuyruk öğelerinin ayrıntılarını gösteren saniyede bir anlık görüntü alınan bir satır kümesi gösterilir. Bazen işe yarıyor ve diğer zamanlarda hiç çalışmıyor.
Sezon 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. Oturum
/* 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. Oturum
/* 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
Oturum 4 ve üstü - istediğiniz kadar
/* 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
benim QueueHistory tabloya veri yakalamak için benim komut dosyası ile hiçbir şey yapmıyor olmasıdır. Acaba StatusID işlenmedi mi? Kullanıyor WITH (NOLOCK)
çalışması gerekir yani teorik ... ve daha önce iş mi! Neden şimdi çalışmadığından emin değilim, ama muhtemelen başka bir öğrenme deneyimi.