CẬP NHẬT hiệu suất khi không có dữ liệu thay đổi


31

Nếu tôi có một UPDATEtuyên bố không thực sự thay đổi bất kỳ dữ liệu nào (vì dữ liệu đã ở trạng thái cập nhật). Có bất kỳ lợi ích hiệu suất trong việc đặt một kiểm tra trong WHEREđiều khoản để ngăn chặn cập nhật?

Ví dụ, sẽ có bất kỳ sự khác biệt về tốc độ thực hiện giữa CẬP NHẬT 1 và CẬP NHẬT 2 trong các điều sau:

CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
    (1, 1),
    (2, 2),
    (3, 3);

-- UPDATE 1
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2
    AND Value <> 2;
SELECT @@ROWCOUNT;

-- UPDATE 2
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2;
SELECT @@ROWCOUNT;

DROP TABLE MyTable;

Lý do tôi hỏi là tôi cần số hàng để bao gồm hàng không thay đổi để tôi biết có nên thực hiện chèn nếu ID không tồn tại. Như vậy tôi đã sử dụng mẫu CẬP NHẬT 2. Nếu có lợi ích về hiệu suất khi sử dụng biểu mẫu CẬP NHẬT 1, có thể lấy số hàng mà tôi cần bằng cách nào đó không?


Xem sqlperformance.com/2012/10/t-sql-queries/conditable-updates (mặc dù tôi không mô tả trường hợp không có giá trị thay đổi).
Aaron Bertrand

Câu trả lời:


24

Nếu tôi có một câu lệnh CẬP NHẬT không thực sự thay đổi bất kỳ dữ liệu nào (vì dữ liệu đã ở trạng thái cập nhật), có bất kỳ lợi ích hiệu suất nào trong việc đặt một kiểm tra trong mệnh đề where để ngăn cập nhật không?

Chắc chắn có thể có một sự khác biệt hiệu suất nhỏ do CẬP NHẬT 1 :

  • không thực sự cập nhật bất kỳ hàng nào (do đó không có gì để ghi vào đĩa, thậm chí không hoạt động nhật ký tối thiểu) và
  • lấy ra các khóa ít hạn chế hơn những gì được yêu cầu để thực hiện cập nhật thực tế (do đó tốt hơn cho đồng thời) ( Vui lòng xem phần Cập nhật đến cuối )

Tuy nhiên, bạn cần đo lường bao nhiêu sự khác biệt trên hệ thống của mình với lược đồ, dữ liệu và tải hệ thống. Có một số yếu tố ảnh hưởng đến mức độ ảnh hưởng của việc CẬP NHẬT không cập nhật:

  • số lượng tranh chấp trên bảng được cập nhật
  • số lượng hàng được cập nhật
  • nếu có CẬP NHẬT Kích hoạt trên bảng đang được cập nhật (như được Mark ghi chú trong một nhận xét về Câu hỏi). Nếu bạn thực thi UPDATE TableName SET Field1 = Field1, thì Trình kích hoạt cập nhật sẽ kích hoạt và cho biết trường đã được cập nhật (nếu bạn kiểm tra bằng các hàm UPDATE () hoặc COLUMNS_UPDATED ) và trường trong cả hai INSERTEDDELETEDbảng đều có cùng giá trị.

Ngoài ra, phần tóm tắt sau đây được tìm thấy trong bài viết của Paul White, Tác động của việc không cập nhật cập nhật (như được lưu ý bởi @spaghettidba trong một bình luận về câu trả lời của ông):

SQL Server chứa một số tối ưu hóa để tránh việc ghi nhật ký hoặc xóa trang không cần thiết khi xử lý thao tác CẬP NHẬT sẽ không dẫn đến bất kỳ thay đổi nào đối với cơ sở dữ liệu liên tục.

  • Các bản cập nhật không cập nhật vào bảng cụm thường tránh ghi nhật ký và xóa trang thêm, trừ khi một cột hình thành (một phần) khóa cụm bị ảnh hưởng bởi hoạt động cập nhật.
  • Nếu bất kỳ phần nào của khóa cụm được 'cập nhật' thành cùng một giá trị, thao tác được ghi lại như thể dữ liệu đã thay đổi và các trang bị ảnh hưởng được đánh dấu là bẩn trong nhóm bộ đệm. Đây là kết quả của việc chuyển đổi CẬP NHẬT sang thao tác xóa-sau đó chèn.
  • Các bảng heap hoạt động giống như các bảng được phân cụm, ngoại trừ chúng không có khóa cụm để gây ra bất kỳ việc ghi nhật ký hoặc xóa trang nào. Đây vẫn là trường hợp ngay cả khi khóa chính không phân cụm tồn tại trên heap. Do đó, các bản cập nhật không cập nhật thành một đống thường tránh việc ghi nhật ký và xả thêm (nhưng xem bên dưới).
  • Cả heaps và các bảng được phân cụm sẽ chịu sự ghi nhật ký và xóa thêm cho bất kỳ hàng nào trong đó một cột LOB chứa hơn 8000 byte dữ liệu được cập nhật thành cùng một giá trị bằng cách sử dụng bất kỳ cú pháp nào ngoài 'SET cột_name = cột_name'.
  • Đơn giản chỉ cần kích hoạt một trong hai mức độ cách ly phiên bản hàng trên cơ sở dữ liệu luôn gây ra việc ghi nhật ký và xóa thêm. Điều này xảy ra bất kể mức cô lập có hiệu lực đối với giao dịch cập nhật.

Xin lưu ý (đặc biệt nếu bạn không theo liên kết để xem toàn bộ bài viết của Paul), hai mục sau:

  1. Các bản cập nhật không cập nhật vẫn có một số hoạt động nhật ký, cho thấy một giao dịch đang bắt đầu và kết thúc. Chỉ là không có sửa đổi dữ liệu xảy ra (vẫn là một khoản tiết kiệm tốt).

  2. Như tôi đã nói ở trên, bạn cần kiểm tra trên hệ thống của bạn. Sử dụng các truy vấn nghiên cứu tương tự mà Paul đang sử dụng và xem nếu bạn nhận được kết quả tương tự. Tôi đang thấy kết quả hơi khác trên hệ thống của tôi so với những gì được hiển thị trong bài viết. Vẫn không có trang bẩn để viết, nhưng một chút hoạt động đăng nhập.


... Tôi cần số lượng hàng để bao gồm hàng không thay đổi để tôi biết có nên chèn hay không nếu ID không tồn tại. ... Có thể lấy số hàng mà tôi cần bằng cách nào đó không?

Đơn giản, nếu bạn chỉ giao dịch với một hàng đơn, bạn có thể làm như sau:

UPDATE MyTable
SET    Value = 2
WHERE  ID = 2
AND Value <> 2;

IF (@@ROWCOUNT = 0)
BEGIN
  IF (NOT EXISTS(
                 SELECT *
                 FROM   MyTable
                 WHERE  ID = 2 -- or Value = 2 depending on the scenario
                )
     )
  BEGIN
     INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
     VALUES (2, 2);
  END;
END;

Đối với nhiều hàng, bạn có thể lấy thông tin cần thiết để đưa ra quyết định bằng cách sử dụng OUTPUTmệnh đề. Bằng cách nắm bắt chính xác các hàng đã được cập nhật, sau đó bạn có thể thu hẹp các mục để tìm kiếm để biết sự khác biệt giữa các hàng không cập nhật không tồn tại trái ngược với không cập nhật các hàng tồn tại nhưng không cần cập nhật.

Tôi chỉ ra cách thực hiện cơ bản trong câu trả lời sau:

Làm cách nào để tránh sử dụng truy vấn Hợp nhất khi tăng nhiều dữ liệu bằng tham số xml?

Phương thức hiển thị trong câu trả lời đó không lọc ra các hàng tồn tại mà không cần phải cập nhật. Phần đó có thể được thêm vào, nhưng trước tiên bạn cần hiển thị chính xác nơi bạn sẽ nhận được tập dữ liệu mà bạn đang hợp nhất MyTable. Có phải họ đến từ một cái bàn tạm thời? Một tham số có giá trị bảng (TVP)?


CẬP NHẬT 1:

Cuối cùng tôi đã có thể thực hiện một số thử nghiệm và đây là những gì tôi tìm thấy liên quan đến nhật ký giao dịch và khóa. Đầu tiên, lược đồ cho bảng:

CREATE TABLE [dbo].[Test]
(
  [ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
  [StringField] [varchar](500) NULL
);

Tiếp theo, kiểm tra cập nhật trường thành giá trị mà nó đã có:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117

Các kết quả:

-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
8 - IX          6 - PAGE
5 - X           7 - KEY

Cuối cùng, kiểm tra lọc ra bản cập nhật do giá trị không thay đổi:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117
AND    rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';

Các kết quả:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
7 - IU          6 - PAGE
4 - U           7 - KEY

Như bạn có thể thấy, không có gì được ghi vào Nhật ký giao dịch khi lọc ra hàng, trái ngược với hai mục đánh dấu điểm bắt đầu và kết thúc của Giao dịch. Và trong khi sự thật là hai mục đó gần như không có gì, chúng vẫn là một cái gì đó.

Ngoài ra, việc khóa tài nguyên PAGE và KEY ít hạn chế hơn khi lọc ra các hàng không thay đổi. Nếu không có quá trình nào khác tương tác với bảng này thì có lẽ đó không phải là vấn đề (nhưng thực sự có khả năng như thế nào?). Hãy nhớ rằng thử nghiệm được hiển thị trong bất kỳ blog nào được liên kết (và thậm chí cả thử nghiệm của tôi) mặc nhiên cho rằng không có sự tranh chấp nào trên bàn vì nó không bao giờ là một phần của thử nghiệm. Nói rằng các bản cập nhật không cập nhật có trọng lượng nhẹ đến mức không cần phải thực hiện quá trình lọc cần phải được thực hiện bằng một hạt muối kể từ khi thử nghiệm được thực hiện, ít nhiều, trong chân không. Nhưng trong sản xuất, bảng này rất có thể không bị cô lập. Tất nhiên, rất có thể là một chút đăng nhập và các khóa hạn chế hơn không chuyển thành hiệu quả thấp hơn. Vì vậy, nguồn thông tin đáng tin cậy nhất để trả lời câu hỏi này? Máy chủ SQL. Đặc biệt:Máy chủ SQL của bạn . Nó sẽ cho bạn thấy phương pháp nào tốt hơn cho hệ thống của bạn :-).


CẬP NHẬT 2:

Nếu các hoạt động trong đó giá trị mới giống với giá trị hiện tại (nghĩa là không cập nhật), hãy đánh số các hoạt động trong đó giá trị mới là khác nhau và cập nhật là cần thiết, thì mẫu sau có thể chứng minh thậm chí còn tốt hơn, đặc biệt là nếu có rất nhiều tranh cãi trên bàn Ý tưởng là làm một đơn giản SELECTđầu tiên để có được giá trị hiện tại. Nếu bạn không nhận được một giá trị thì bạn có câu trả lời của bạn về INSERT. Nếu bạn không có một giá trị, bạn có thể làm một cách đơn giản IFvà ban hành các UPDATE chỉ nếu nó là cần thiết.

DECLARE @CurrentValue VARCHAR(500) = NULL,
        @NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
        @ID INT = 4082117;

SELECT @CurrentValue = rt.StringField
FROM   dbo.Test rt
WHERE  rt.ID = @ID;

IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
  -- row does not exist
  INSERT INTO dbo.Test (ID, StringField)
  VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
  -- row exists, so check value to see if it is different
  IF (@CurrentValue <> @NewValue)
  BEGIN
    -- value is different, so do the update
    UPDATE rt
    SET    rt.StringField = @NewValue
    FROM   dbo.Test rt
    WHERE  rt.ID = @ID;
  END;
END;

Các kết quả:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (2 Lock:Acquired events):
Mode            Type
--------------------------------------
6 - IS          5 - OBJECT
6 - IS          6 - PAGE

Vì vậy, chỉ có 2 khóa được mua thay vì 3 và cả hai khóa này là Intent Shared, không phải Intent eXinating hay Intent Update ( Khóa tương thích ). Hãy nhớ rằng mỗi khóa thu được cũng sẽ được phát hành, mỗi khóa thực sự là 2 thao tác, vì vậy phương pháp mới này có tổng cộng 4 thao tác thay vì 6 thao tác trong phương thức đề xuất ban đầu. Xem xét hoạt động này đang chạy một lần cứ sau 15 ms (xấp xỉ, như đã nêu của OP), tức là khoảng 66 lần mỗi giây. Vì vậy, đề xuất ban đầu lên tới 396 thao tác khóa / mở khóa mỗi giây, trong khi phương pháp mới này chỉ có 264 thao tác khóa / mở khóa mỗi giây đối với các khóa có trọng lượng nhẹ hơn. Đây không phải là một đảm bảo về hiệu suất tuyệt vời, nhưng chắc chắn đáng để thử nghiệm :-).


14

Thu nhỏ một chút và suy nghĩ về bức tranh lớn hơn. Trong thế giới thực, tuyên bố cập nhật của bạn thực sự sẽ trông như thế này:

UPDATE MyTable
  SET Value = 2
WHERE
     ID = 2
     AND Value <> 2;

Hoặc nó sẽ trông giống như thế này:

UPDATE Customers
  SET AddressLine1 = '123 Main St',
      AddressLine2 = 'Apt 24',
      City = 'Chicago',
      State = 'IL',
      (and a couple dozen more fields)
WHERE
     ID = 2
     AND (AddressLine1 <> '123 Main St'
     OR AddressLine2 <> 'Apt 24'
     OR City <> 'Chicago'
     OR State <> 'IL'
      (and a couple dozen more fields))

Bởi vì trong thế giới thực, các bảng có rất nhiều cột. Điều đó có nghĩa là bạn sẽ phải tạo ra rất nhiều logic ứng dụng động phức tạp để xây dựng các chuỗi động, HOẶC bạn sẽ phải chỉ định nội dung trước và sau của mọi trường, mọi lúc.

Nếu bạn xây dựng các câu lệnh cập nhật này một cách linh hoạt cho mỗi bảng, chỉ chuyển qua các trường đang được cập nhật, bạn có thể nhanh chóng gặp phải vấn đề ô nhiễm bộ đệm bộ đệm tương tự như vấn đề kích thước tham số NHibernate từ vài năm trước. Tệ hơn nữa, nếu bạn xây dựng các câu lệnh cập nhật trong SQL Server (như trong các thủ tục được lưu trữ), thì bạn sẽ ghi các chu kỳ CPU quý giá vì SQL Server không hiệu quả khủng khiếp trong việc nối các chuỗi với nhau theo tỷ lệ.

Do những sự phức tạp đó, thường không có ý nghĩa gì khi thực hiện loại so sánh theo từng hàng, từng lĩnh vực này khi bạn thực hiện các bản cập nhật. Hãy nghĩ rằng các hoạt động dựa trên thiết lập thay thế.


1
Ví dụ thế giới thực của tôi đơn giản như thế nhưng được gọi rất nhiều. Ước tính của tôi là cứ sau 15ms vào thời gian cao điểm. Tôi đã tự hỏi nếu SQL Server là cleaver đủ để không ghi vào đĩa khi không cần thiết.
Martin Brown

3

Bạn có thể thấy mức tăng hiệu suất trong việc bỏ qua các hàng không cần cập nhật chỉ khi số lượng hàng lớn (ít ghi nhật ký, ít trang bẩn hơn để ghi vào đĩa).

Khi xử lý các cập nhật hàng đơn như trong trường hợp của bạn, sự khác biệt hiệu suất là không đáng kể. Nếu cập nhật các hàng trong mọi trường hợp giúp bạn dễ dàng hơn, hãy làm điều đó.

Để biết thêm thông tin về chủ đề này, xem Cập nhật không cập nhật của Paul White



1

Thay vì kiểm tra giá trị của tất cả các trường, bạn không thể lấy giá trị băm bằng cách sử dụng các cột bạn quan tâm sau đó so sánh giá trị đó với hàm băm được lưu trữ so với hàng trong bảng?

IF EXISTS (Select 1 from Table where ID =@ID AND HashValue=Sha256(column1+column2))
GOTO EXIT
ELSE
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.