Các chiến lược để kiểm tra các bản ghi của chế độ xử lý


10

Tôi không chắc có một mẫu được đặt tên cho cái này không, hoặc nếu không bởi vì đó là một ý tưởng tồi tệ. Nhưng tôi cần dịch vụ của mình để hoạt động trong môi trường cân bằng tải hoạt động / hoạt động. Đây chỉ là máy chủ applicaiton. Cơ sở dữ liệu sẽ nằm trên một máy chủ riêng biệt. Tôi có một dịch vụ sẽ cần chạy qua một quy trình cho mỗi bản ghi trong một bảng. Quá trình này có thể mất một hoặc hai phút và sẽ lặp lại sau mỗi n phút (có thể định cấu hình, thường là 15 phút).

Với một bảng gồm 1000 bản ghi cần xử lý này và hai dịch vụ chạy cùng bộ dữ liệu này, tôi muốn mỗi dịch vụ "kiểm tra" một bản ghi để xử lý. Tôi cần đảm bảo rằng chỉ có một dịch vụ / luồng xử lý mỗi bản ghi tại một thời điểm.

Tôi có các đồng nghiệp đã sử dụng "bảng khóa" trong quá khứ. Trường hợp một bản ghi được ghi vào bảng này để khóa một cách hợp lý bản ghi trong bảng khác (bảng khác đó là btw khá tĩnh và với một bản ghi mới rất thường xuyên được thêm vào), sau đó xóa để giải phóng khóa.

Tôi tự hỏi liệu bảng này có tốt hơn không khi có một cột cho biết khi nào nó bị khóa và nó hiện đang bị khóa, thay vì chèn liên tục xóa.

Có ai có một lời khuyên cho loại điều này? Có một mô hình được thiết lập cho khóa logic dài hạn (ish) không? Bất kỳ lời khuyên cho làm thế nào để đảm bảo chỉ có một dịch vụ lấy khóa tại một thời điểm? (Đồng nghiệp của tôi sử dụng TABLOCKX để khóa toàn bộ bảng.)

Câu trả lời:


12

Tôi không phải là một fan hâm mộ lớn của bảng "khóa" thêm hoặc ý tưởng khóa toàn bộ bảng để lấy bản ghi tiếp theo. Tôi hiểu lý do tại sao nó được thực hiện, nhưng điều đó cũng làm tổn hại đến sự đồng thời cho các hoạt động đang cập nhật để phát hành một bản ghi bị khóa (chắc chắn hai quy trình không thể chiến đấu với điều đó khi hai quy trình không thể khóa cùng một bản ghi tại cùng thời gian).

Sở thích của tôi sẽ là thêm một cột ProcessStatusID (thường là TINYINT) vào bảng với dữ liệu đang được xử lý. Và có một lĩnh vực cho LastModifiedDate? Nếu không, thì nó nên được thêm vào. Nếu có, thì những hồ sơ này có được cập nhật ngoài quá trình xử lý này không? Nếu các bản ghi có thể được cập nhật bên ngoài quy trình cụ thể này, thì nên thêm một trường khác để theo dõi StatusModifiedDate (hoặc một cái gì đó tương tự). Đối với phần còn lại của câu trả lời này, tôi sẽ chỉ sử dụng "StatusModifiedDate" vì nó rõ ràng theo nghĩa của nó (và trên thực tế, có thể được sử dụng làm tên trường ngay cả khi hiện tại không có trường "LastModifiedDate").

Các giá trị cho ProcessStatusID (cần được đặt vào bảng tra cứu mới có tên "ProcessStatus" và Foreign Keyed cho bảng này) có thể là:

  1. Đã hoàn thành (hoặc thậm chí "Đang chờ xử lý" trong trường hợp này vì cả hai đều có nghĩa là "sẵn sàng để được xử lý")
  2. Đang xử lý (hoặc "Đang xử lý")
  3. Lỗi (hoặc "WTF?")

Tại thời điểm này, có vẻ an toàn khi cho rằng từ ứng dụng, nó chỉ muốn lấy bản ghi tiếp theo để xử lý và sẽ không chuyển bất cứ điều gì để giúp đưa ra quyết định đó. Vì vậy, chúng tôi muốn lấy bản ghi cũ nhất (ít nhất là về StatusModifiedDate) được đặt thành "Đã hoàn thành" / "Đang chờ xử lý". Một cái gì đó dọc theo dòng:

SELECT TOP 1 pt.RecordID
FROM   ProcessTable pt
WHERE  pt.StatusID = 1
ORDER BY pt.StatusModifiedDate ASC;

Chúng tôi cũng muốn cập nhật bản ghi đó thành "Đang xử lý" cùng một lúc để ngăn quá trình khác lấy nó. Chúng tôi có thể sử dụng OUTPUTmệnh đề để cho phép chúng tôi thực hiện CẬP NHẬT và CHỌN trong cùng một giao dịch:

UPDATE TOP (1) pt
SET    pt.StatusID = 2,
       pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID
FROM   ProcessTable pt
WHERE  pt.StatusID = 1;

Vấn đề chính ở đây là trong khi chúng ta có thể làm một TOP (1)trong một UPDATEhoạt động, không có cách nào để làm một ORDER BY. Nhưng, chúng ta có thể gói nó trong CTE để kết hợp hai khái niệm đó:

;WITH cte AS
(
   SELECT TOP 1 pt.RecordID
   FROM   ProcessTable pt (READPAST, ROWLOCK, UPDLOCK)
   WHERE  pt.StatusID = 1
   ORDER BY pt.StatusModifiedDate ASC;
)
UPDATE cte
SET    cte.StatusID = 2,
       cte.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID;

Câu hỏi rõ ràng là liệu hai quá trình thực hiện CHỌN cùng một lúc có thể lấy cùng một bản ghi hay không. Tôi khá chắc chắn rằng mệnh đề CẬP NHẬT với OUTPUT, đặc biệt là kết hợp với các gợi ý READPAST và UPDLOCK (xem bên dưới để biết thêm chi tiết), sẽ ổn. Tuy nhiên, tôi chưa thử nghiệm kịch bản chính xác này. Nếu vì một lý do nào đó, truy vấn trên không quan tâm đến điều kiện cuộc đua, thì thêm ý chí sau: khóa ứng dụng.

Truy vấn CTE ở trên có thể được gói trong sp_getapplocksp_releaseapplock để tạo "trình giữ cổng" cho quy trình. Khi làm như vậy, chỉ có một quy trình tại một thời điểm sẽ có thể nhập để chạy truy vấn ở trên. Quá trình khác sẽ bị chặn cho đến khi quá trình với applock giải phóng nó. Và vì bước này của quy trình tổng thể chỉ là để lấy RecordID, nên nó khá nhanh và sẽ không chặn quá trình khác trong một thời gian dài. Và, giống như với truy vấn CTE, chúng tôi không chặn toàn bộ bảng, do đó cho phép các cập nhật khác cho các hàng khác (để đặt trạng thái của chúng thành "Đã hoàn thành" hoặc "Lỗi"). Bản chất:

BEGIN TRANSACTION;
EXEC sp_getapplock @Resource = 'GetNextRecordToProcess', @LockMode = 'Exclusive';

   {CTE UPDATE query shown above}

EXEC sp_releaseapplock @Resource = 'GetNextRecordToProcess';
COMMIT TRANSACTION;

Khóa ứng dụng rất đẹp nhưng nên sử dụng một cách tiết kiệm.

Cuối cùng, bạn chỉ cần một thủ tục được lưu trữ để xử lý cài đặt trạng thái thành "Đã hoàn thành" hoặc "Lỗi". Và đó có thể là một điều đơn giản:

CREATE PROCEDURE ProcessTable_SetProcessStatusID
(
   @RecordID INT,
   @ProcessStatusID TINYINT
)
AS
SET NOCOUNT ON;

UPDATE pt
SET    pt.ProcessStatusID = @ProcessStatusID,
       pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
FROM   ProcessTable pt
WHERE  pt.RecordID = @RecordID;

Gợi ý bảng (được tìm thấy tại Gợi ý (Transact-SQL) - Bảng ):

  • READPAST (dường như phù hợp với kịch bản chính xác này)

    Chỉ định rằng Công cụ cơ sở dữ liệu không đọc các hàng bị khóa bởi các giao dịch khác. Khi READPAST được chỉ định, các khóa cấp hàng bị bỏ qua. Đó là, Cơ sở dữ liệu bỏ qua các hàng thay vì chặn giao dịch hiện tại cho đến khi các khóa được giải phóng ... READPAST chủ yếu được sử dụng để giảm tranh chấp khóa khi thực hiện hàng đợi công việc sử dụng bảng SQL Server. Trình đọc hàng đợi sử dụng READPAST bỏ qua các mục hàng đợi bị khóa bởi các giao dịch khác sang mục nhập hàng đợi có sẵn tiếp theo, mà không phải đợi cho đến khi các giao dịch khác giải phóng khóa của chúng.

  • ROWLOCK (chỉ để an toàn)

    Chỉ định rằng khóa hàng được thực hiện khi khóa trang hoặc bảng được thực hiện thông thường.

  • UPDLOCK

    Chỉ định rằng các khóa cập nhật sẽ được thực hiện và giữ cho đến khi giao dịch hoàn tất. UPDLOCK có các khóa cập nhật cho các hoạt động đọc chỉ ở cấp độ hàng hoặc cấp trang.


1

Đã làm điều tương tự (không có ứng dụng, hoàn toàn trong DB) bằng cách sử dụng hàng đợi của Nhà môi giới dịch vụ. Nhẹ, hoàn toàn tuân thủ ACID, có thể được thu nhỏ gần như vô tận. Khóa hàng trong suốt (hoặc "ẩn", thay vào đó) được tích hợp sẵn. Có sẵn từ phiên bản 2005 trở đi.

Trong trường hợp của bạn, kiến ​​trúc tổng thể có thể như thế này: một số quy trình gửi tin nhắn vào hộp thoại Nhà môi giới dịch vụ, theo lịch trình của họ và người nghe chọn chúng từ hàng đợi ở phía đích. Ngoài việc tạo các loại thông báo riêng biệt, bạn có thể bao gồm khá nhiều thứ vào phần thân thư - ví dụ như thời gian chờ và bất kỳ tham số nào mà tác vụ có thể có.

Không phải là điều dễ dàng nhất để nắm bắt, đó là điều chắc chắn, nhưng một khi bạn có được nó, lợi thế của nó sẽ trở nên rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.