2016-01-08 11 views
7

Theo số UPDATE documentation, UPDATE luôn mua một khóa độc quyền trên toàn bộ bảng. Tuy nhiên, tôi tự hỏi nếu khóa độc quyền được mua trước khi các hàng được cập nhật được xác định hoặc chỉ ngay trước khi cập nhật thực tế.Giao dịch chỉ cập nhật một bảng đơn luôn bị cô lập?

vấn đề cụ thể của tôi là tôi có một lồng nhau SELECT trong UPDATE của tôi như thế này:

UPDATE Tasks 
SET Status = 'Active' 
WHERE Id = (SELECT TOP 1 Id 
      FROM Tasks 
      WHERE Type = 1 
       AND (SELECT COUNT(*) 
        FROM Tasks 
        WHERE Status = 'Active') = 0 
      ORDER BY Id) 

Bây giờ tôi tự hỏi liệu nó có thực sự được đảm bảo rằng có đúng một nhiệm vụ với Status = 'Active' sau đó nếu song song các tuyên bố tương tự có thể được thực hiện với một Loại khác:

UPDATE Tasks 
SET Status = 'Active' 
WHERE Id = (SELECT TOP 1 Id 
      FROM Tasks 
      WHERE Type = 2   -- <== The only difference 
       AND (SELECT COUNT(*) 
        FROM Tasks 
        WHERE Status = 'Active') = 0 
      ORDER BY Id) 

Nếu cả hai câu lệnh thay đổi hàng sẽ được xác định trước khi khóa được mua, tôi có thể kết thúc với hai nhiệm vụ hoạt động mà tôi phải ngăn chặn.

Nếu đúng như vậy, làm cách nào để ngăn chặn? Tôi có thể ngăn chặn nó mà không đặt mức giao dịch thành SERIALIZABLE hoặc gây rối với các gợi ý khóa không?

Từ câu trả lời cho Is a single SQL Server statement atomic and consistent? Tôi đã học được rằng vấn đề phát sinh khi lồng nhau SELECT truy cập vào một bảng khác. Tuy nhiên, tôi không chắc liệu tôi có quan tâm đến vấn đề này hay không nếu chỉ có bảng cập nhật có liên quan.

+1

tài liệu Đó là sai anyway. Cập nhật thường sẽ không khóa toàn bộ bảng. –

+0

Hm, ok, cảm ơn. Nhưng tôi tìm tài liệu chính xác ở đâu? – lex82

+0

Tài liệu không thực sự nói rằng 'UPDATE' khóa toàn bộ bảng. Nó nói rằng nó mua lại một khóa độc quyền, nhưng một khóa độc quyền không có trên toàn bộ bảng. –

Trả lời

0

Không, ít nhất câu lệnh chọn lồng nhau có thể được xử lý trước khi bắt đầu cập nhật và có khóa. Để đảm bảo rằng không có truy vấn nào can thiệp vào bản cập nhật này, cần thiết đặt mức cách ly giao dịch thành SERIALIZABLE.

Bài viết này (và hàng loạt nó là một phần của) giải thích rất rõ sự tinh tế của đồng thời trong SQL server:

http://sqlperformance.com/2014/02/t-sql-queries/confusion-caused-by-trusting-acid

1

Đây không phải là một câu trả lời về câu hỏi của bạn ... Nhưng câu hỏi của bạn là nỗi đau cho đôi mắt của tôi :)

;WITH cte AS 
(
    SELECT *, RowNum = ROW_NUMBER() OVER (PARTITION BY [type] ORDER BY id) 
    FROM Tasks 
) 
UPDATE cte 
SET [Status] = 'Active' 
WHERE RowNum = 1 
    AND [type] = 1 
    AND NOT EXISTS(
      SELECT 1 
      FROM Tasks 
      WHERE [Status] = 'Active' 
     ) 
+1

Có thể truy vấn ban đầu của OP hiệu quả hơn phiên bản này. Lưu ý: Giải pháp của OP nên sử dụng 'NOT EXISTS' thay vì' (SELECT COUNT (*)...) '. –

+0

Tôi thích RowNum nhưng tôi thấy IsActive khó hiểu vì nó không liên quan đến hàng cụ thể. Tôi sẽ sử dụng một tuyên bố có điều kiện nhưng tôi khá chắc chắn rằng điều này sẽ mang lại chính xác những vấn đề tôi muốn tránh vì bảng sẽ không bị khóa. – lex82

+0

@Gordon Linoff cảm ơn ghi chú – Devart

6

Nếu bạn muốn chính xác một nhiệm vụ với tĩnh = active, sau đó thiết lập các bảng để đảm bảo điều này là đúng. Sử dụng chỉ mục duy nhất được lọc:

create unique index unq_tasks_status_filter_active on tasks(status) 
    where status = 'Active'; 

Đồng thời thứ hai update có thể không thành công, nhưng bạn sẽ được đảm bảo tính duy nhất. Mã ứng dụng của bạn có thể xử lý các cập nhật không thành công như vậy và thử lại.

Dựa vào kế hoạch thực thi thực tế của các bản cập nhật có thể nguy hiểm. Đó là lý do tại sao an toàn hơn để cơ sở dữ liệu thực hiện các xác nhận hợp lệ như vậy. Chi tiết triển khai bên dưới có thể khác nhau, tùy thuộc vào môi trường và phiên bản của SQL Server. Ví dụ, những gì làm việc trong một môi trường xử lý đơn luồng, đơn lẻ có thể không hoạt động trong môi trường song song. Điều gì làm việc với một mức cô lập có thể không hoạt động với một mức khác.

EDIT:

Và, tôi không thể cưỡng lại. Đối với mục đích hiệu quả, xem xét bằng văn bản cho truy vấn như:

UPDATE Tasks 
    SET Status = 'Active' 
    WHERE NOT EXISTS (SELECT 1 
         FROM Tasks 
         WHERE Status = 'Active' 
        ) AND 
      Id = (SELECT TOP 1 Id 
       FROM Tasks 
       WHERE Type = 2   -- <== The only difference 
       ORDER BY Id 
       ); 

Sau đó, nơi chỉ số về Tasks(Status)Tasks(Type, Id). Trong thực tế, với truy vấn đúng, bạn có thể thấy rằng truy vấn quá nhanh (bất chấp cập nhật trên chỉ mục) mà bạn lo lắng về các bản cập nhật hiện tại được giảm thiểu đáng kể. Điều này sẽ không giải quyết được một tình trạng chạy đua, nhưng ít nhất nó có thể làm cho nó trở nên hiếm hoi.

Và nếu bạn đang chụp lỗi, sau đó với chỉ số lọc duy nhất, bạn có thể chỉ cần làm:

UPDATE Tasks 
    SET Status = 'Active' 
    WHERE Id = (SELECT TOP 1 Id 
       FROM Tasks 
       WHERE Type = 2   -- <== The only difference 
       ORDER BY Id 
       ); 

này sẽ trả về một lỗi nếu liên tiếp đã được kích hoạt.

Lưu ý: tất cả các truy vấn và khái niệm này có thể được áp dụng cho "một hoạt động cho mỗi nhóm". Câu trả lời này đang giải quyết câu hỏi mà bạn đã hỏi. Nếu bạn gặp vấn đề "một hoạt động trên mỗi nhóm", hãy cân nhắc đặt một câu hỏi khác.

+0

Cảm ơn bạn đã chỉ ra điều này! Thật không may điều này là không thể trong môi trường tôi đang làm việc in tuyên bố của tôi đã được đơn giản hóa anyway vì vậy tôi không chắc chắn nếu như một chỉ số sẽ giúp đỡ nếu tôi có thể thiết lập nó. Tôi muốn hiểu cách UPDATE được xử lý như thế nào. – lex82

+1

@ lex82 - nếu đơn giản hóa, ví dụ: rằng chỉ có thể có một hoạt động cho mỗi (kết hợp một hoặc nhiều cột khác) thì bạn chỉ có các cột đó trong chỉ mục thay vì 'trạng thái'. –

Các vấn đề liên quan