Bản ghi trùng lặp được trả về từ bảng không có bản sao


8

Tôi có một thủ tục được lưu trữ truy vấn một bảng xếp hàng bận rộn được sử dụng để phân phối công việc trong hệ thống của chúng tôi. Bảng trong câu hỏi có khóa chính trên WorkID và không trùng lặp.

Một phiên bản đơn giản của truy vấn là:

INSERT INTO #TempWorkIDs (WorkID)
SELECT
        W.WorkID

    FROM
        dbo.WorkTable W

    WHERE
        (@bool_param = 0 AND
        ((W.InProgress = 0
         AND ISNULL(W.UserID, -1) != @userid_param
         AND (@bool_filtered = 0
              OR W.TypeID IN (SELECT TypeID FROM #Types AS t)))
         OR 
         (@bool_param = 1
          AND W.InProgress = 1
          AND W.UserID != @userid_param)
        OR
        (@Auto_Param = 0
         AND W.UserID = @userid_param)))
         OR
         (@bool_param = 1 AND W.UserID = @userid_param)
    OPTION
        (RECOMPILE)

Các #Typesbảng được phổ biến trước đó trong thủ tục.

Như tôi đã nói, WorkTableđang bận và đôi khi trong khi truy vấn này đang chạy, tôi TẠM một trong các bản ghi đang di chuyển từ một bộ các bộ lọc sang bộ lọc WHEREkhác. Cụ thể, điều này xảy ra khi ai đó bắt đầu làm việc trên một mục và W.InProgressthay đổi từ 0 thành 1. Khi điều này xảy ra, tôi bị vi phạm khóa trùng lặp khi tôi cố thêm khóa chính vào bảng tạm thời mà truy vấn này đang chèn vào.

Tôi đã xác nhận trong kế hoạch truy vấn được tạo khi xảy ra lỗi không có sự song song, mức cô lập READ COMMITTEDvà không có bản ghi trùng lặp trong bảng nguồn. Bạn cũng có thể thấy không có JOINcách nào khác để có được các sản phẩm cartesian ở đây.

Đây là kế hoạch truy vấn ẩn danh:

nhập mô tả hình ảnh ở đây

Câu hỏi là, điều gì gây ra sự trùng lặp và làm thế nào tôi có thể dừng nó lại?

Tôi nghĩ READ COMMITTEDnên làm việc ở đây, tôi cần khóa. Tôi gần như tích cực các bản sao xảy ra khi InProgressbit trên bản ghi thay đổi trong khi tôi truy vấn. Tôi biết điều này bởi vì bảng lưu trữ thời gian của sự thay đổi đó và nó chỉ trong một phần nghìn giây khi tôi truy vấn và gặp lỗi.

Câu trả lời:


9

Có một số tình huống khó khăn mà có thể dẫn đến việc cùng hàng được đọc hai lần từ một chỉ số, ngay cả dưới các READ COMMITTEDmức độ cách ly .

Truy vấn của bạn không đủ điều kiện để quét thứ tự phân bổ, vì vậy công cụ lưu trữ sẽ đọc dữ liệu từ bảng theo thứ tự của khóa cụm.

Đối với bảng của bạn, bạn có InProgresscột đầu tiên của khóa cụm. Có khả năng bạn đang bị khóa hàng hoặc khóa trang khi bạn quét qua bảng. Nếu bạn đọc một hàng gần khi bắt đầu quét, hãy mở khóa trên đó, hàng đó được cập nhật sao cho InProgressthay đổi từ 0 thành 1, và sau đó hàng được đọc lại trong một trang khác thì bạn có thể thấy WorkIDcác giá trị trùng lặp từ truy vấn của mình .

Có rất nhiều cách giải quyết. Bạn có thể chèn vào một đống và chỉ cần loại bỏ các giá trị trùng lặp. Bạn có thể thêm một DISTINCTtruy vấn. Bạn cũng có thể kích hoạt mức cô lập phiên bản hàng, để cung cấp chế độ xem ổn định về trạng thái đã cam kết của cơ sở dữ liệu, ngay khi bắt đầu giao dịch ( cách ly ảnh chụp nhanh ) hoặc khi bắt đầu câu lệnh ( đọc cách ly ảnh chụp đã cam kết ).

Có lẽ nó phù hợp để thêm gợi ý khóa hoặc thay đổi cấu trúc của bảng. Đối với một giải pháp khá thú vị (có thể không phù hợp với sản xuất), bạn có thể thử đọc chỉ mục ngược. Điều này có thể được thực hiện với một không cần thiết TOPcùng với một ORDER BY. Dưới đây là một bản demo rất đơn giản để minh họa điểm:

CREATE TABLE #WorkTable (
    InProgress TINYINT NOT NULL,
    WorkID INT NOT NULL
    , PRIMARY KEY (InProgress, WorkID)
);

INSERT INTO #WorkTable WITH (TABLOCK)
SELECT (RN - 1) / 5000, RN
FROM
(
    SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t
OPTION (MAXDOP 1);

Truy vấn sau đây có thuộc tính Ordered: false nhưng nó vẫn sẽ đọc dữ liệu theo thứ tự khóa cụm:

SELECT WorkId
FROM #WorkTable;

Tuy nhiên, truy vấn sau đây sẽ đọc dữ liệu theo thứ tự cụm ngược lại:

SELECT TOP (9223372036854775807) WorkId
FROM #WorkTable
ORDER BY InProgress DESC, WorkId DESC;

Chúng ta có thể thấy điều này bằng cách nhìn vào các thuộc tính quét:

quét ngược

Đối với bảng của bạn, điều này có nghĩa là nếu một hàng được cập nhật sao cho InProgressthay đổi từ 0 thành 1 thì sẽ ít có khả năng nó sẽ hiển thị hai lần. Nó có thể không hiển thị ở tất cả có thể là một vấn đề khác nhau.

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.