Tôi đã cố gắng trả lời câu hỏi stackoverflow sau đây:
Sau khi đăng một câu trả lời hơi ngây thơ, tôi nghĩ rằng tôi đã đặt tiền của mình vào miệng và thực sự kiểm tra kịch bản mà tôi đang đề xuất, để chắc chắn rằng tôi đã không gửi OP ra khỏi một cuộc rượt đuổi ngông cuồng. Chà, hóa ra nó khó hơn nhiều so với tôi nghĩ (không có gì ngạc nhiên với bất cứ ai, tôi chắc chắn).
Đây là những gì tôi đã thử và nghĩ về:
Đầu tiên tôi đã thử CẬP NHẬT TOP 1 với ĐẶT HÀNG BỞI trong bảng dẫn xuất, sử dụng
ROWLOCK, READPAST
. Điều này mang lại bế tắc và cũng xử lý các mặt hàng không theo thứ tự. Nó phải càng gần với FIFO càng tốt, loại bỏ các lỗi yêu cầu cố gắng xử lý cùng một hàng nhiều lần.Sau đó tôi đã cố gắng chọn mong muốn QueueID tiếp theo vào một biến, sử dụng kết hợp khác nhau của
READPAST
,UPDLOCK
,HOLDLOCK
, vàROWLOCK
để độc quyền duy trì hàng để cập nhật bởi phiên đó. Tất cả các biến thể tôi đã thử chịu các vấn đề tương tự như trước đây cũng như đối với các kết hợp nhất định vớiREADPAST
, phàn nàn:Bạn chỉ có thể chỉ định khóa READPAST ở các mức cách ly READ READ CAMEDED hoặc REPEATABLE READ.
Điều này đã gây nhầm lẫn bởi vì nó đã được đọc . Tôi đã gặp phải điều này trước đây và nó đang bực bội.
Kể từ khi tôi bắt đầu viết câu hỏi này, Remus Rusani đã đăng một câu trả lời mới cho câu hỏi. Tôi đã đọc bài viết được liên kết của anh ấy và thấy rằng anh ấy đang sử dụng các bài đọc phá hoại, vì anh ấy đã nói trong câu trả lời của mình rằng "thực tế không thể giữ khóa trong suốt thời gian của các cuộc gọi web." Sau khi đọc những gì bài báo của anh ấy nói về các điểm nóng và các trang yêu cầu khóa để thực hiện bất kỳ cập nhật hoặc xóa nào, tôi sợ rằng ngay cả khi tôi có thể tìm ra các khóa chính xác để làm những gì tôi đang tìm kiếm, nó sẽ không thể mở rộng và có thể mở rộng không xử lý đồng thời lớn.
Ngay bây giờ tôi không chắc chắn nơi để đi. Có đúng là việc duy trì các khóa trong khi hàng được xử lý không thể đạt được (ngay cả khi nó không hỗ trợ tps cao hoặc đồng thời lớn)? Tôi đang thiếu gì?
Với hy vọng rằng những người thông minh hơn tôi và những người có kinh nghiệm hơn tôi có thể giúp đỡ, dưới đây là kịch bản thử nghiệm mà tôi đang sử dụng. Nó được chuyển trở lại phương thức CẬP NHẬT HÀNG ĐẦU 1 nhưng tôi đã để lại phương thức khác, nhận xét, trong trường hợp bạn cũng muốn khám phá điều đó.
Dán từng thứ này vào một phiên riêng biệt, chạy phiên 1, sau đó nhanh chóng tất cả các phiên khác. Trong khoảng 50 giây, bài kiểm tra sẽ kết thúc. Nhìn vào Tin nhắn từ mỗi phiên để xem nó hoạt động như thế nào (hoặc nó thất bại như thế nào). Phiên đầu tiên sẽ hiển thị một hàng với một ảnh chụp nhanh được thực hiện một lần thứ hai chi tiết các khóa hiện tại và các mục hàng đợi đang được xử lý. Nó hoạt động đôi khi, và những lần khác không hoạt động.
Phiên 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;
*/
Đợt 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
Buổi 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
Phiên 4 trở lên - bao nhiêu tùy thích
/* 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
kịch bản của tôi để thu thập dữ liệu vào bảng QueueHistory thì không làm gì cả. Tôi tự hỏi nếu đó là vì StatusID không được cam kết? Về WITH (NOLOCK)
mặt lý thuyết, nó nên hoạt động ... và nó đã hoạt động trước đây! Tôi không chắc tại sao bây giờ nó không hoạt động, nhưng có lẽ đó là một kinh nghiệm học tập khác.