Hiệu suất khủng khiếp khi tham gia các bảng CHỈ VÀ XÓA trong một trình kích hoạt


12

Tôi đã có một trình kích hoạt CẬP NHẬT trên một bảng theo dõi một cột cụ thể thay đổi từ một giá trị cụ thể sang bất kỳ giá trị nào khác. Khi điều này xảy ra, nó cập nhật một số dữ liệu liên quan trong một bảng khác thông qua một câu lệnh CẬP NHẬT duy nhất.

Điều đầu tiên mà trình kích hoạt thực hiện là kiểm tra xem liệu có bất kỳ hàng cập nhật nào có giá trị của cột này thay đổi so với giá trị được đề cập hay không. Nó chỉ đơn giản là tham gia INSERTED để DELETED và so sánh giá trị trong cột đó. Nếu không có gì đủ điều kiện, nó sẽ giải cứu sớm để câu lệnh CẬP NHẬT không chạy.

IF NOT EXISTS (
    SELECT TOP 1 i.CUSTNMBR
    FROM INSERTED i
        INNER JOIN DELETED d
            ON i.CUSTNMBR = d.CUSTNMBR
    WHERE d.CUSTCLAS = 'Misc'
        AND i.CUSTCLAS != 'Misc'
)
    RETURN

Trong trường hợp này, CUSTNMBR là khóa chính của bảng bên dưới. Nếu tôi thực hiện một bản cập nhật lớn trên bảng này (giả sử, hơn 5000 hàng), tuyên bố này sẽ có LẠI, ngay cả khi tôi chưa chạm vào cột TÙY CHỈNH. Tôi có thể xem nó bị đình trệ trong tuyên bố này trong vài phút trong Profiler.

Kế hoạch thực hiện thật kỳ quái. Nó hiển thị Quét được chèn với 3.714 lần thực hiện và ~ 18,5 triệu hàng đầu ra. Điều đó chạy qua một bộ lọc trên cột CUSTCLAS. Nó kết hợp điều này (thông qua vòng lặp lồng nhau) với Quét đã xóa (cũng được lọc trên CUSTCLAS), chỉ thực hiện một lần và có 5000 hàng đầu ra.

Những điều ngu ngốc tôi đang làm ở đây để gây ra điều này? Lưu ý rằng kích hoạt hoàn toàn phải xử lý đúng cập nhật nhiều hàng.

CHỈNH SỬA :

Tôi cũng đã thử viết nó như thế này (trong trường hợp EXISTS đang làm điều gì đó khó chịu), nhưng nó vẫn tệ như vậy.

DECLARE @CUSTNMBR varchar(31)
SELECT TOP 1 @CUSTNMBR = i.CUSTNMBR
FROM INSERTED i
    INNER JOIN DELETED d
        ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
    AND i.CUSTCLAS != 'Misc'

IF @CUSTNMBR IS NULL
    RETURN

Bạn có thể thoát khỏi "TOP 1" không? Tôi nghĩ rằng điều đó gây ra một số chi phí có thể không cần thiết nếu bạn chỉ kiểm tra xem có trường hợp nào không ...
JHFB

Câu trả lời:


10

Bạn có thể đánh giá bằng cách sử dụng các gợi ý rõ ràng INNER MERGE JOINhoặc INNER HASH JOINgợi ý nhưng có lẽ bạn sẽ sử dụng lại các bảng này sau đó trong trình kích hoạt, có lẽ tốt hơn hết là bạn chỉ cần chèn nội dung inserteddeletedbảng vào #tempcác bảng được lập chỉ mục và được thực hiện với nó.

Họ không nhận được các chỉ mục hữu ích được tạo tự động cho họ.


Được rồi, điều này tăng tốc nó lên rất nhiều, tuy nhiên có khả năng thực hiện kích hoạt tầng. Nếu tôi sử dụng cùng tên bảng tạm thời (#i, #d) trong mỗi kích hoạt, chúng sẽ xung đột. Có một giải pháp tốt hơn / an toàn hơn là chỉ sử dụng một tên bảng tạm thời khác nhau trong mỗi kích hoạt?
db2

Có thể đánh giá bằng cách sử dụng các biến bảng (với khóa chính được xác định CUSTNMBRđể tạo chỉ mục cụm duy nhất) và sử dụng OPTION (RECOMPILE)gợi ý để lấy nó để tính đến số lượng hàng hoặc có thể chỉ sử dụng một quy ước đặt tên cụ thể, chẳng hạn như#i_dbo_YourTable
Martin Smith

Tôi nghĩ rằng tôi sẽ giải quyết cho việc đặt tên cho họ như thế nào #trigger_name_i. Nếu tôi đi với các biến bảng, tôi sẽ phải làm lộn xộn mã hơn nữa với các BẢNG TẠO rõ ràng. Chúng tôi đã có các trình kích hoạt xếp tầng, nhưng không kích hoạt đệ quy, vì vậy tôi nghĩ rằng tôi sẽ an toàn ...
db2

Tôi đề nghị một biến bảng thay vì bảng tạm thời cho mục đích này; các biến bảng vẫn có thể có các chỉ mục chính và phụ (duy nhất), chúng được tự động dọn sạch khi kích hoạt thoát và các biến bảng được đặt trong phạm vi thực thi kích hoạt đó (nó sẽ không xung đột với các biến bảng khác có cùng tên cao hơn hoặc thấp hơn ngăn xếp cuộc gọi). Để tiết kiệm chi phí mã định nghĩa bảng, hãy xác định loại bảng cho từng loại và sử dụng tên loại để khai báo các biến của bảng.
Chris Smith

@ChrisSmith bạn cũng thường sẽ cần OPTION (RECOMPILE)vì vậy tính chính xác của thẻ.
Martin Smith

10

Tôi biết điều này đã được trả lời nhưng nó chỉ xuất hiện khi hoạt động gần đây và tôi cũng đã chạy vào đây cho các bảng có nhiều triệu hàng. Mặc dù không giảm giá cho câu trả lời được chấp nhận, ít nhất tôi có thể thêm rằng kinh nghiệm của tôi cho thấy rằng một yếu tố chính trong hiệu suất Kích hoạt khi thực hiện các thử nghiệm tương tự (xem liệu một hoặc nhiều cột có thực sự thay đổi giá trị của chúng hay không) là liệu cột có hay không đang được thử nghiệm thực sự là một phần của UPDATEtuyên bố. Tôi thấy rằng việc so sánh các cột giữa inserteddeletedcác bảng trong thực tế không phải là một phần của UPDATEtuyên bố đã tạo ra một lực cản rất lớn đối với hiệu suất mà nếu không thì các trường đó là một phần củaUPDATEtuyên bố (bất kể giá trị của chúng thực sự được thay đổi). Tại sao tất cả các công việc đó (tức là một truy vấn để so sánh N trường trên các hàng X) để xác định xem có gì thay đổi hay không nếu bạn có thể loại trừ một cách hợp lý khả năng bất kỳ cột nào bị thay đổi, điều này rõ ràng là không thể nếu chúng không xuất hiện trong SETmệnh đề của UPDATEtuyên bố.

Giải pháp mà tôi sử dụng là sử dụng hàm UPDATE () chỉ hoạt động bên trong Triggers. Hàm dựng sẵn này cho bạn biết nếu một cột được chỉ định trong UPDATEcâu lệnh và có thể được sử dụng để thoát Kích hoạt nếu các cột mà bạn quan tâm không phải là một phần của UPDATE. Điều này có thể được sử dụng cùng với a SELECTđể xác định xem các cột đó, giả sử rằng chúng có mặt trong UPDATE, có thay đổi thực tế hay không. Tôi có mã ở đầu một số trình kích hoạt kiểm toán trông giống như:

-- exit on updates that do not update the only 3 columns we ETL
IF (
     EXISTS(SELECT 1 FROM DELETED) -- this is an UPDATE (Trigger is AFTER INSERT, UPDATE)
     AND (
            NOT (UPDATE(Column3) OR UPDATE(Column7)
                 OR UPDATE(Column11)) -- the columns we care about are not being updated
            OR NOT EXISTS(
                        SELECT 1
                        FROM INSERTED ins
                        INNER JOIN DELETED del
                                ON del.KeyField1 = ins.KeyField1
                                AND del.KeyField2 = ins.KeyField2
                        WHERE ins.Column3 <> del.Column3
                                 COLLATE Latin1_General_100_CS_AS -- case-sensitive compare
                        OR    ISNULL(ins.Column7, -99) <> 
                                 ISNULL(del.Column7, -99) -- NULLable INT field
                        OR    ins.[Column11] <> del.[Column11] -- NOT NULL INT field
                      )
          )
    )
BEGIN
    RETURN;
END;

Logic này sẽ tiến hành phần còn lại của kích hoạt nếu:

  1. Các hoạt động là một INSERT
  2. Ít nhất một trong các trường có liên quan nằm trong SETmệnh đề của một UPDATE ít nhất một trong số các cột trong một hàng đã thay đổi

Cái này NOT (UPDATE...) OR NOT EXISTS()có thể trông kỳ lạ hoặc ngược, nhưng nó được thiết kế để tránh thực hiện SELECTtrên inserteddeletedbảng nếu không có cột nào liên quan là một phần của UPDATE.

Tùy thuộc vào nhu cầu của bạn, hàm COLUMNS_UPDATED () là một tùy chọn khác để xác định cột nào là một phần của UPDATEcâu lệnh.


1
Điểm hay là họ nên kiểm tra UPDATE(CUSTCLAS)và bỏ qua toàn bộ nếu sai (+1). Tôi không nghĩ rằng bạn đúng rằng các cột không cập nhật không có sẵn trong các phiên bản hàng như các cột được cập nhật.
Martin Smith

@MartinSmith, làm thế nào để chúng ta chứng minh điều này bằng cách này hay cách khác? Mặc dù, nó có thể không quan trọng nếu hành vi có thể dự đoán theo cách mà tôi đã tìm thấy. Tôi chỉ biết rằng đó là một sự khác biệt hiệu suất mạnh mẽ khi thực hiện cùng một CHỌN, THAM GIA giữa CHỨNG MINH và XÓA, kiểm tra các trường để biết sự khác biệt thực tế, tùy thuộc vào việc các trường trong WHERE có trong TẬP HỢP hay không. Hành vi tôi đã thấy là nhất quán, do đó là lý thuyết của tôi, nhưng sẽ rất tốt / thú vị khi biết lý do thực sự. Tôi nghi ngờ rằng các trường không có trong SET phải quay lại bảng cơ sở để biết giá trị của chúng.
Solomon Rutzky

Tôi đã xem xét cấu trúc của điều này trước đây. Tôi không thể nhớ nếu tôi tìm thấy một cách tốt để làm việc đó hay tôi chỉ được sử dụng một cách dễ dàng tìm chuỗi có khả năng và một cuộc tìm kiếm đầy đủ thông qua tempdbvớiDBCC PAGE
Martin Smith

ĐỒNG Ý. Trong một ví dụ với một tệp có kích thước tối thiểu, tempdbtôi vừa thử tập lệnh này , dán đầu ra vào notepad và tìm kiếm "EEEEEE". Tôi thấy đầu ra trong ảnh chụp màn hình ở đây . Lưu ý các phiên bản trước và sau của cả hai cột trong cả hai hàng. Có thể có nhiều cách dễ dàng hơn nhưng đủ cho mục đích của tôi ở đây!
Martin Smith

Mặc dù trên thực tế, có các chuỗi EEEEEE dài khác trong các tempdbtrang không bên cạnh BBBBBBhoặc DDDDDD. Có thể phải làm một số điều tra thêm! Mặc dù có lẽ điều này là do REPLICATEcuộc gọi.
Martin Smith

2

Tôi có thể thử viết lại bằng cách sử dụng nếu tồn tại

IF EXISTS (SELECT TOP 1 i.CUSTNMBR     
            FROM INSERTED i         
            INNER JOIN DELETED d             
            ON i.CUSTNMBR = d.CUSTNMBR and d.custclass = 'Misc'  
            WHERE d.CUSTCLAS <>i.CUSTCLAS)    
BEGIN

--do your triggerstuff here
END

1

http://dave.brittens.org/blog/wr-well-behatted-triggers.html

Theo Dave, bạn nên sử dụng các bảng tạm thời hoặc các biến bảng với các chỉ mục, bởi vì các bảng XÁC NHẬN / XÓA ảo không có. Nếu bạn có khả năng kích hoạt đệ quy, thì bạn nên sử dụng biến bảng để tránh xung đột tên.

Hy vọng ai đó thấy điều này hữu ích vì bài viết gốc đã khá lâu rồi ...


-1

Đoạn mã sau có thể làm tăng hiệu suất của trình kích hoạt này. Tôi không biết loại dữ liệu chính xác của cột [lớp học] vì vậy bạn cần điều chỉnh nó.

DECLARE @i AS TABLE (CUSTNMBR VARCHAR(31) NOT NULL PRIMARY KEY, custclass VARCHAR(10) NOT NULL)
DECLARE @d AS TABLE (CUSTNMBR VARCHAR(31) NOT NULL PRIMARY KEY, custclass VARCHAR(10) NOT NULL)
INSERT INTO @i SELECT CUSTNMBR, custclass FROM inserted
INSERT INTO @d SELECT CUSTNMBR, custclass FROM deleted
IF NOT EXISTS
  (SELECT * FROM @i AS i INNER JOIN @d AS d ON d.CUSTNMBR = i.CUSTNMBR
   WHERE i.custclass <> d.custclass) RETURN

Lưu ý rằng bạn có thể bao gồm các cột bổ sung trong các cột này trong các bản sao bộ nhớ của các bảng được chènxóa nếu bạn cần chúng trong mã kích hoạt của mình. Các khóa chính trên các bảng này sẽ tăng đáng kể hiệu suất tham gia khi cập nhật nhiều hơn một vài hàng cùng một lúc. Chúc may mắn!

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.