Chèn Cập nhật proc được lưu trữ trên SQL Server


104

Tôi đã viết một proc được lưu trữ sẽ thực hiện cập nhật nếu bản ghi tồn tại, nếu không nó sẽ thực hiện chèn. Nó trông giống như sau:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

Logic của tôi đằng sau việc viết nó theo cách này là bản cập nhật sẽ thực hiện một lựa chọn ngầm bằng cách sử dụng mệnh đề where và nếu điều đó trả về 0 thì việc chèn sẽ diễn ra.

Cách thay thế để thực hiện theo cách này sẽ là thực hiện một lựa chọn và sau đó dựa trên số hàng được trả về hoặc thực hiện cập nhật hoặc chèn. Điều này tôi coi là không hiệu quả vì nếu bạn cập nhật, nó sẽ gây ra 2 lựa chọn (lệnh gọi chọn rõ ràng đầu tiên và ẩn thứ hai ở nơi cập nhật). Nếu proc thực hiện chèn thì sẽ không có sự khác biệt về hiệu quả.

Logic của tôi có âm thanh ở đây không? Đây có phải là cách bạn kết hợp chèn và cập nhật vào một proc được lưu trữ không?

Câu trả lời:


61

Giả định của bạn là đúng, đây là cách tối ưu để thực hiện và nó được gọi là upert / merge .

Tầm quan trọng của UPSERT - từ sqlservercentral.com :

Đối với mỗi bản cập nhật trong trường hợp được đề cập ở trên, chúng tôi sẽ xóa một lần đọc bổ sung khỏi bảng nếu chúng tôi sử dụng UPSERT thay vì EXISTS. Thật không may cho một Phụ trang, cả hai phương pháp UPSERT và IF EXISTS đều sử dụng cùng một số lần đọc trên bảng. Do đó, việc kiểm tra sự tồn tại chỉ nên được thực hiện khi có một lý do rất xác đáng để biện minh cho I / O bổ sung. Cách tối ưu hóa để thực hiện mọi việc là đảm bảo rằng bạn có ít lượt đọc nhất có thể trên DB.

Chiến lược tốt nhất là cố gắng cập nhật. Nếu không có hàng nào bị ảnh hưởng bởi bản cập nhật thì hãy chèn. Trong hầu hết các trường hợp, hàng sẽ tồn tại và chỉ cần một I / O.

Chỉnh sửa : Vui lòng xem câu trả lời này và bài đăng trên blog được liên kết để tìm hiểu về các vấn đề với mẫu này và cách làm cho nó hoạt động an toàn.


1
Tôi nghĩ nó đã trả lời được ít nhất một câu hỏi. Và tôi đã không thêm mã vì mã trong câu hỏi dường như đã phù hợp với tôi. Mặc dù tôi sẽ đưa nó vào một giao dịch, nhưng tôi không tính đến mức độ cô lập cho bản cập nhật. Cảm ơn vì đã chỉ ra điều đó trong câu trả lời của bạn!
binOr

54

Vui lòng đọc bài đăng trên blog của tôi để biết một mẫu tốt, an toàn mà bạn có thể sử dụng. Có rất nhiều cân nhắc và câu trả lời được chấp nhận cho câu hỏi này là không an toàn.

Để có câu trả lời nhanh, hãy thử mẫu sau. Nó sẽ hoạt động tốt trên SQL 2000 trở lên. SQL 2005 cung cấp cho bạn xử lý lỗi mở ra các tùy chọn khác và SQL 2008 cung cấp cho bạn lệnh MERGE.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

1
Trong bài đăng trên blog của mình, bạn kết thúc với việc sử dụng gợi ý WITH (updlock, serializable) trong kiểm tra sự tồn tại. Tuy nhiên, đọc MSDN là trạng thái: "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." Điều này có nghĩa là gợi ý có thể tuần tự hóa là không cần thiết vì dù sao thì khóa cập nhật sẽ được giữ cho phần còn lại của giao dịch, hay tôi đã hiểu nhầm điều gì đó?
Dan Def

10

Nếu được sử dụng với SQL Server 2000/2005, mã gốc cần được bao gồm trong giao dịch để đảm bảo rằng dữ liệu vẫn nhất quán trong kịch bản đồng thời.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

Điều này sẽ phát sinh thêm chi phí hiệu suất, nhưng sẽ đảm bảo tính toàn vẹn của dữ liệu.

Thêm, như đã được đề xuất, MERGE nên được sử dụng nếu có.



6

Bạn không chỉ cần chạy nó trong giao dịch, nó còn cần mức độ cô lập cao. Tôi thực tế, mức độ cách ly mặc định là Đã đọc và mã này cần Có thể nối tiếp.

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

Có thể thêm cả kiểm tra lỗi @@ và khôi phục có thể là một ý tưởng hay.


@Munish Goyal Bởi vì trong cơ sở dữ liệu nhiều lệnh và tiền lệ chạy trong paralel. Sau đó, luồng khác có thể chèn một hàng ngay sau khi chạy cập nhật và trước khi chạy chèn.
Tomas Tintera

5

Nếu bạn không thực hiện hợp nhất trong SQL 2008, bạn phải thay đổi nó thành:

nếu @@ rowcount = 0 và @@ error = 0

ngược lại nếu cập nhật không thành công vì lý do nào đó thì nó sẽ thử và chèn sau đó vì số hàng trên một câu lệnh không thành công là 0


3

Người hâm mộ lớn của UPSERT, thực sự cắt giảm mã để quản lý. Đây là một cách khác tôi làm điều đó: Một trong các tham số đầu vào là ID, nếu ID là NULL hoặc 0, bạn biết đó là INSERT, nếu không thì đó là bản cập nhật. Giả sử ứng dụng biết nếu có ID, vì vậy sẽ không hoạt động trong mọi tình huống, nhưng sẽ cắt giảm một nửa số lần thực thi nếu bạn làm vậy.


2

Bài đăng của Dima Malenko được sửa đổi:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

Bạn có thể mắc lỗi và gửi bản ghi đến một bảng chèn không thành công.
Tôi cần làm điều này vì chúng tôi đang lấy bất kỳ dữ liệu nào được gửi qua WSDL và nếu có thể sẽ sửa nó trong nội bộ.


1

Logic của bạn có vẻ hợp lý, nhưng bạn có thể muốn xem xét thêm một số mã để ngăn việc chèn nếu bạn đã nhập một khóa chính cụ thể.

Ngược lại, nếu bạn luôn thực hiện chèn nếu bản cập nhật không ảnh hưởng đến bất kỳ bản ghi nào, điều gì sẽ xảy ra khi ai đó xóa bản ghi trước khi bạn chạy "UPSERT"? Hiện tại, bản ghi bạn đang cố gắng cập nhật không tồn tại, vì vậy thay vào đó nó sẽ tạo bản ghi. Đó có thể không phải là hành vi bạn đang tìm kiếm.

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.