2010-02-16 34 views
33

Có khá thường xảy ra khi bạn cần thực thi câu lệnh INSERT, UPDATE hoặc DELETE dựa trên một số điều kiện. Và câu hỏi của tôi là liệu ảnh hưởng đến hiệu năng của truy vấn thêm IF EXISTS trước lệnh hay chưa.NẾU EXISTS trước INSERT, UPDATE, DELETE để tối ưu hóa

Ví dụ

IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1) 
    UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1 

gì về chèn hoặc xóa?

+0

Tại sao không xem xét kế hoạch thực hiện? – RichardOD

Trả lời

65

Tôi không hoàn toàn chắc chắn, nhưng tôi có cảm tưởng rằng câu hỏi này thực sự là khoảng upsert, đó là hoạt động nguyên tử sau:

  • Nếu hàng tồn tại trong cả hai nguồn và đích, UPDATE sự Mục tiêu;
  • Nếu hàng chỉ tồn tại trong nguồn, INSERT hàng vào mục tiêu;
  • (Tùy chọn) Nếu hàng tồn tại trong mục tiêu nhưng không phải nguồn, DELETE hàng từ mục tiêu.

Developers-quay-DBA thường ngây thơ viết nó chèo-by-hàng, như thế này:

-- For each row in source 
IF EXISTS(<target_expression>) 
    IF @delete_flag = 1 
     DELETE <target_expression> 
    ELSE 
     UPDATE target 
     SET <target_columns> = <source_values> 
     WHERE <target_expression> 
ELSE 
    INSERT target (<target_columns>) 
    VALUES (<source_values>) 

Đây là chỉ là về điều tồi tệ nhất bạn có thể làm, vì nhiều lý do:

  • Nó có một điều kiện chủng tộc. Hàng có thể biến mất giữa IF EXISTSDELETE hoặc UPDATE tiếp theo.

  • Thật lãng phí. Đối với mỗi giao dịch bạn có một hoạt động bổ sung đang được thực hiện; có lẽ nó tầm thường, nhưng điều đó phụ thuộc hoàn toàn vào mức độ bạn đã lập chỉ mục.

  • Tồi tệ nhất - nó đang theo mô hình lặp lại, suy nghĩ về những vấn đề này ở cấp độ của một hàng. Điều này sẽ có tác động lớn nhất (tồi tệ nhất) của tất cả về hiệu suất tổng thể.

Tối ưu hóa rất nhỏ (và tôi nhấn mạnh) là chỉ cố gắng UPDATE; nếu hàng không tồn tại, @@ROWCOUNT sẽ là 0 và bạn có thể sau đó "một cách an toàn" chèn:

-- For each row in source 
BEGIN TRAN 

UPDATE target 
SET <target_columns> = <source_values> 
WHERE <target_expression> 

IF (@@ROWCOUNT = 0) 
    INSERT target (<target_columns>) 
    VALUES (<source_values>) 

COMMIT 

trường hợp xấu nhất, điều này sẽ vẫn thực hiện hai hoạt động cho mỗi giao dịch, nhưng ít nhất có một cơ hội của chỉ thực hiện một, và nó cũng loại bỏ các điều kiện chủng tộc (loại).

Nhưng vấn đề thực sự là vấn đề này vẫn đang được thực hiện cho mỗi hàng trong nguồn.

Trước khi SQL Server 2008, bạn phải sử dụng một mô hình 3-giai đoạn khó khăn khi phải đối phó với điều này ở cấp bộ (vẫn còn tốt hơn so với hàng-by-hàng): hiệu suất

BEGIN TRAN 

INSERT target (<target_columns>) 
SELECT <source_columns> FROM source s 
WHERE s.id NOT IN (SELECT id FROM target) 

UPDATE t SET <target_columns> = <source_columns> 
FROM target t 
INNER JOIN source s ON t.d = s.id 

DELETE t 
FROM target t 
WHERE t.id NOT IN (SELECT id FROM source) 

COMMIT 

Như tôi đã nói, là khá tệ hại về điều này, nhưng vẫn còn tốt hơn rất nhiều so với cách tiếp cận một hàng-tại-một-thời gian.SQL Server 2008, tuy nhiên, cuối cùng đã giới thiệu cú pháp MERGE, vì vậy bây giờ tất cả những gì bạn phải làm là:

MERGE target 
USING source ON target.id = source.id 
WHEN MATCHED THEN UPDATE <target_columns> = <source_columns> 
WHEN NOT MATCHED THEN INSERT (<target_columns>) VALUES (<source_columns>) 
WHEN NOT MATCHED BY SOURCE THEN DELETE; 

Vậy đó. Một tuyên bố. Nếu bạn đang sử dụng SQL Server 2008 và cần phải thực hiện bất kỳ chuỗi INSERT, UPDATEDELETE tuỳ thuộc vào việc hay không hàng đã tồn tại - ngay cả khi nó chỉ là một hàng - có không lý do gì không được sử dụng MERGE .

Bạn thậm chí có thể OUTPUT các hàng bị ảnh hưởng bởi một MERGE thành biến bảng nếu bạn cần tìm hiểu sau đó những gì đã được thực hiện. Đơn giản, nhanh chóng và không có rủi ro. Làm đi.

+2

Cảm ơn bạn đã giới thiệu tôi với MERGE. Nó thực sự rất tuyệt. – jwarzech

+7

MERGE rất tuyệt vời - muốn chỉ ra vì tôi vừa mới phát hiện ra điều này: MERGE không tránh Điều kiện Race trừ khi một gợi ý thích hợp được thêm vào ('WITH (HOLDLOCK)'). Xem công việc của [Dan Guzman về việc chỉ ra Điều kiện Race UPSERT với MERGE] (http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx) –

+1

+ 1 cho "Nhà phát triển biến DBA thường vô tình viết nó theo từng hàng"! –

2

Việc thực hiện một tuyên bố IF EXISTS:

IF EXISTS(SELECT 1 FROM mytable WHERE someColumn = someValue) 

phụ thuộc vào các chỉ số hiện nay để đáp ứng các truy vấn.

3

Bạn không nên thực hiện việc này trong hầu hết các trường hợp. Tùy thuộc vào cấp độ giao dịch của bạn, bạn đã tạo ra một điều kiện chủng tộc, bây giờ trong ví dụ của bạn ở đây nó sẽ không quan trọng đến nhiều, nhưng dữ liệu có thể được thay đổi từ lựa chọn đầu tiên để cập nhật. Và tất cả những gì bạn đã làm là buộc SQL phải làm nhiều việc hơn

Cách tốt nhất để biết chắc chắn là kiểm tra hai sự khác biệt và xem cái nào mang đến cho bạn hiệu suất phù hợp.

2

Có một hiệu ứng nhỏ, kể từ khi bạn đang làm việc kiểm tra hai lần, ít nhất là trong ví dụ của bạn:

IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1) 

Có để truy vấn, xem nếu có bất kỳ, nếu đúng thì:

UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1 

Phải truy vấn, xem cái nào ... kiểm tra cùng hai lần mà không có lý do gì. Bây giờ nếu điều kiện bạn đang tìm kiếm được lập chỉ mục, nó phải nhanh chóng, nhưng đối với các bảng lớn, bạn có thể thấy sự chậm trễ chỉ vì bạn đang chạy lựa chọn.

1

Điều này sẽ ảnh hưởng đến hiệu suất (mức độ hiệu suất sẽ bị ảnh hưởng sẽ bị ảnh hưởng bởi một số yếu tố). Có hiệu quả bạn đang làm cùng một truy vấn "hai lần" (trong ví dụ của bạn). Hãy tự hỏi liệu bạn có cần phòng thủ này trong truy vấn của bạn và trong trường hợp nào thì hàng đó sẽ không ở đó? Ngoài ra, với một tuyên bố cập nhật các hàng bị ảnh hưởng có lẽ là một cách tốt hơn để xác định xem có điều gì đã được cập nhật hay không.

3

IF EXISTS về cơ bản sẽ thực hiện một CHỌN - giống như phiên bản UPDATE sẽ làm.

Vì vậy, nó sẽ giảm hiệu suất - nếu không có gì để cập nhật, bạn đã làm cùng một lượng công việc (UPDATE sẽ truy vấn thiếu hàng như bạn chọn) và nếu có điều gì đó để cập nhật, bạn đã làm một lựa chọn không cần thiết.

+0

Một giải pháp thay thế là câu trả lời của burnall: chỉ cần cập nhật trước và sau đó kiểm tra @@ rowcount. Nếu đó là số không thì không có gì được cập nhật và bạn gọi thông qua một chèn. –

+0

'IF EXISTS' là ** không ** thiết kế tốt cho thao tác chèn hoặc cập nhật. Nó có một điều kiện chủng tộc, ngay cả bên trong một khối 'BEGIN TRAN' /' COMMIT TRAN'. – Aaronaught

+0

@Aaronaught - điểm tốt. Loại bỏ mảnh đó. – DVK

8

Điều đó không hữu ích cho chỉ một bản cập nhật/xóa/chèn.
Có thể thêm hiệu suất nếu một vài toán tử sau nếu điều kiện.
Trong trường hợp cuối cùng viết tốt hơn

update a set .. where .. 
if @@rowcount > 0 
begin 
    .. 
end 
+0

+1. Tôi phải thừa nhận rằng khi đọc câu hỏi đầu tiên, nó không rõ ràng (với tôi ít nhất) rằng đây là những gì OP đã đề cập đến. Phải trễ ... –

+0

Chính xác. Làm những gì sẽ * thường * là hành động cần thiết, kiểm tra kết quả, sau đó thực hiện trường hợp thay thế nếu cần. –

+0

+1 Tôi cũng sẽ đi xuống con đường này nhưng không rõ ràng từ câu hỏi của mình ... Câu trả lời hay là – JoshBerke

4

Bạn không nên làm điều đó cho UPDATEDELETE, như thể có tác động trên hiệu suất, nó là không phải là một tích cực một.

Đối INSERT có thể có những tình huống mà bạn INSERT sẽ nâng cao một ngoại lệ (UNIQUE CONSTRAINT vi phạm vv), trong trường hợp này bạn có thể muốn ngăn chặn nó với IF EXISTS và xử lý nó một cách duyên dáng hơn.

2

này chủ yếu là lặp đi lặp lại trước đó (theo thời gian) năm (không, sáu) (không, bảy) câu trả lời, nhưng:

Vâng, IF EXISTS cấu trúc mà bạn có bằng và lớn sẽ tăng gấp đôi công việc được thực hiện bởi kho dữ liệu. Trong khi IF EXISTS sẽ "dừng" khi nó tìm thấy hàng phù hợp đầu tiên (nó không cần phải tìm tất cả), nó vẫn còn thêm và cuối cùng là nỗ lực vô nghĩa - để cập nhật và xóa.

  • Nếu không có hàng nào tồn tại, NẾU EXISTS sẽ quét toàn bộ (bảng hoặc chỉ mục) để xác định điều này.
  • Nếu một hoặc nhiều hàng như vậy tồn tại, nếu EXISTS sẽ đọc đủ bảng/chỉ mục để tìm bảng đầu tiên, sau đó UPDATE hoặc DELETE sau đó sẽ đọc lại bảng để tìm lại và xử lý nó - và nó sẽ đọc "phần còn lại của" bảng để xem liệu có còn quá trình nào nữa để xử lý không. (Đủ nhanh nếu được lập chỉ mục đúng cách, nhưng vẫn còn.)

Vì vậy, một trong hai cách, bạn sẽ đọc toàn bộ bảng hoặc chỉ mục ít nhất một lần. Nhưng, tại sao lại bận tâm với IF EXISTS ngay từ đầu?

UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1 

hoặc DELETE tương tự sẽ làm việc tốt hay không có bất kỳ hàng tìm thấy để xử lý. Không có hàng, bảng được quét, không có gì sửa đổi, bạn đã hoàn tất; Hơn 1 hàng, bảng được quét, mọi thứ cần được sửa đổi, được thực hiện lại. Một vượt qua, không ồn ào, không ồn ào, không phải lo lắng về "cơ sở dữ liệu có được thay đổi bởi người dùng khác giữa truy vấn đầu tiên của tôi và truy vấn thứ hai của tôi" hay không.

INSERT là tình huống có thể hữu ích - hãy kiểm tra xem hàng có tồn tại trước khi thêm hàng hay không, để tránh vi phạm Chính hoặc Độc đáo. Tất nhiên bạn phải lo lắng về đồng thời - nếu một người khác đang cố thêm hàng này cùng lúc với bạn thì sao? Gói này tất cả vào một INSERT đơn sẽ xử lý tất cả trong một giao dịch ngầm (nhớ tính chất ACID của bạn!):

INSERT Contacs (col1, col2, etc) values (val1, val2, etc) where not exists (select 1 from Contacs where col1 = val1) 
IF @@rowcount = 0 then <didn't insert, process accordingly> 
4

Cả

UPDATE … IF (@@ROWCOUNT = 0) INSERT 

cũng không

IF EXISTS(...) UPDATE ELSE INSERT 

mẫu làm việc như mong đợi dưới sự đồng thời cao. Cả hai đều có thể thất bại. Cả hai có thể thất bại rất thường xuyên. MERGE là vua - nó nắm giữ tốt hơn nhiều. Hãy để chúng tôi làm một số thử nghiệm căng thẳng và xem cho chính mình.

Dưới đây là bảng chúng ta sẽ được sử dụng:

CREATE TABLE dbo.TwoINTs 
    (
     ID INT NOT NULL PRIMARY KEY, 
     i1 INT NOT NULL , 
     i2 INT NOT NULL , 
     version ROWVERSION 
    ) ; 
GO 

INSERT INTO dbo.TwoINTs 
     (ID, i1, i2) 
VALUES (1, 0, 0) ;  

IF EXISTS (...) THEN mẫu thường xuyên thất bại dưới đồng thời cao.

Hãy để chúng tôi chèn hoặc cập nhật hàng trong vòng lặp bằng cách sử dụng logic đơn giản sau đây: nếu hàng có ID đã cho tồn tại, cập nhật ID và chèn một hàng mới. Vòng lặp sau thực hiện logic này. Cắt và dán nó thành hai tab, chuyển sang chế độ văn bản trong cả hai tab và chạy chúng cùng một lúc.

-- hit Ctrl+T to execute in text mode 

SET NOCOUNT ON ; 

DECLARE @ID INT ; 

SET @ID = 0 ; 
WHILE @ID > -100000 
    BEGIN ; 
     SET @ID = (SELECT MIN(ID) 
        FROM dbo.TwoINTs 
       ) - 1 ; 
     BEGIN TRY ; 

      BEGIN TRANSACTION ; 
      IF EXISTS (SELECT * 
         FROM dbo.TwoINTs 
         WHERE ID = @ID) 
       BEGIN ; 
        UPDATE dbo.TwoINTs 
        SET  i1 = 1 
        WHERE ID = @ID ; 
       END ; 
      ELSE 
       BEGIN ; 
        INSERT INTO dbo.TwoINTs 
          (ID, i1, i2) 
        VALUES (@ID, 0, 0) ; 
       END ; 
      COMMIT ; 
     END TRY 
     BEGIN CATCH ; 
      ROLLBACK ; 
      SELECT error_message() ; 
     END CATCH ; 
    END ; 

Khi chúng tôi chạy tập lệnh này đồng thời trong hai tab, chúng tôi sẽ ngay lập tức nhận được một số lượng lớn các vi phạm chính trong cả hai tab. Điều này cho thấy mô hình IF EXISTS không đáng tin cậy như thế nào khi nó thực hiện dưới sự đồng thời cao. Lưu ý: ví dụ này cũng chứng minh rằng không an toàn khi sử dụng SELECT MAX (ID) +1 hoặc SELECT MIN (ID) -1 làm giá trị duy nhất có sẵn tiếp theo nếu chúng ta thực hiện nó theo sự đồng thời.

0
IF EXISTS....UPDATE 

Đừng làm điều đó. Nó buộc hai lần quét/tìm kiếm thay vì một lần quét.

Nếu cập nhật không tìm thấy kết quả khớp với mệnh đề WHERE, chi phí của bản cập nhật chỉ là tìm kiếm/quét.

Nếu nó tìm thấy kết quả phù hợp và nếu bạn mở đầu bằng w/IF EXISTS, nó phải tìm cùng một kết quả hai lần. Và trong một môi trường đồng thời, điều gì là đúng đối với EXISTS có thể không còn đúng với UPDATE nữa.

Đây chính là lý do tại sao câu lệnh UPDATE/DELETE/INSERT cho phép mệnh đề WHERE. Sử dụng nó!