Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

UPSERT nguyên tử trong SQL Server 2005

INSERT INTO <table>
SELECT <natural keys>, <other stuff...>
FROM <table>
WHERE NOT EXISTS
   -- race condition risk here?
   ( SELECT 1 FROM <table> WHERE <natural keys> )

UPDATE ...
WHERE <natural keys>
  • có một điều kiện chủng tộc trong INSERT đầu tiên. Khóa có thể không tồn tại trong truy vấn bên trong SELECT, nhưng tồn tại tại thời điểm INSERT dẫn đến vi phạm khóa.
  • có một điều kiện chạy đua giữa INSERT và UPDATE. Khóa có thể tồn tại khi được chọn trong truy vấn bên trong của INSERT nhưng sẽ biến mất vào thời điểm UPDATE chạy.

Đối với điều kiện cuộc đua thứ hai, người ta có thể lập luận rằng dù sao thì khóa cũng đã bị xóa bởi chuỗi đồng thời, vì vậy nó không thực sự là một bản cập nhật bị mất.

Giải pháp tối ưu thường là thử trường hợp có khả năng xảy ra nhất và xử lý lỗi nếu nó không thành công (tất nhiên là trong một giao dịch):

  • nếu khóa có thể bị thiếu, hãy luôn chèn trước. Xử lý vi phạm ràng buộc duy nhất, dự phòng để cập nhật.
  • nếu có thể có khóa, hãy luôn cập nhật trước. Chèn nếu không tìm thấy hàng nào. Xử lý vi phạm ràng buộc duy nhất có thể xảy ra, dự phòng để cập nhật.

Bên cạnh tính đúng đắn, mẫu này cũng tối ưu về tốc độ:hiệu quả hơn khi cố gắng chèn và xử lý ngoại lệ hơn là thực hiện các khóa giả mạo. Khóa có nghĩa là đọc trang logic (có thể có nghĩa là đọc trang vật lý) và IO (thậm chí logic) đắt hơn SEH.

Cập nhật @Peter

Tại sao một tuyên bố không phải là 'nguyên tử'? Giả sử chúng ta có một bảng nhỏ:

create table Test (id int primary key);

Bây giờ nếu tôi chạy câu lệnh đơn này từ hai chuỗi, trong một vòng lặp, nó sẽ là 'nguyên tử', như bạn nói, không có điều kiện chủng tộc nào có thể tồn tại:

  insert into Test (id)
    select top (1) id
    from Numbers n
    where not exists (select id from Test where id = n.id); 

Tuy nhiên, chỉ trong vài giây, vi phạm khóa chính xảy ra:

Bản tin thứ 2627, Mức 14, Trạng thái 1, Dòng 4
Vi phạm ràng buộc CHÍNH CHÍNH 'PK__Test__24927208'. Không thể chèn khóa trùng lặp vào đối tượng 'dbo.Test'.

Tại sao vậy? Bạn đúng là kế hoạch truy vấn SQL sẽ thực hiện 'điều đúng đắn' trên DELETE ... FROM ... JOIN , trên WITH cte AS (SELECT...FROM ) DELETE FROM cte và trong nhiều trường hợp khác. Nhưng có một sự khác biệt quan trọng trong những trường hợp này:'truy vấn con' đề cập đến target của một bản cập nhật hoặc xóa hoạt động. Đối với những trường hợp như vậy, kế hoạch truy vấn sẽ thực sự sử dụng một khóa thích hợp, trên thực tế, hành vi này rất quan trọng trong một số trường hợp nhất định, như khi triển khai hàng đợi Sử dụng bảng làm Hàng đợi.

Nhưng trong câu hỏi ban đầu, cũng như trong ví dụ của tôi, truy vấn con được trình tối ưu hóa truy vấn xem như một truy vấn con trong một truy vấn, không phải như một số truy vấn loại 'quét để cập nhật' cần bảo vệ khóa đặc biệt. Kết quả là việc thực hiện tra cứu truy vấn con có thể được quan sát như một hoạt động riêng biệt bởi một người quan sát đồng thời , do đó phá vỡ hành vi 'nguyên tử' của câu lệnh. Trừ khi có biện pháp phòng ngừa đặc biệt, nhiều luồng có thể cố gắng chèn cùng một giá trị, cả hai đều tin rằng họ đã kiểm tra và giá trị không tồn tại. Chỉ có một người có thể thành công, người còn lại sẽ đánh vi phạm PK. QED.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. ExecuteNonQuery:Thuộc tính kết nối chưa được khởi tạo.

  2. Entity Framework 6 - Truy vấn thời gian

  3. Cách loại bỏ Ràng buộc trong SQL Server (T-SQL)

  4. Nhận ngày đầu tiên trong tuần trong SQL Server

  5. Bảng tổng hợp SQL Server với nhiều cột tổng hợp