Tôi đã phân tích "báo cáo lỗi" lặp lại (vấn đề perf) trong một trong các hệ thống của chúng tôi liên quan đến hoạt động xóa đặc biệt chậm. Câu chuyện dài ngắn: Có vẻ như các phím CASCADE DELETE
chịu trách nhiệm chính, và tôi muốn biết (a) nếu điều này có ý nghĩa, và (b) tại sao nó là như vậy.SQL Server DRI (ON DELETE CASCADE) có chậm không?
Chúng tôi có một lược đồ, ví dụ: tiện ích con, những tiện ích đang ở gốc của một biểu đồ lớn các bảng có liên quan và các bảng có liên quan đến tương tự, v.v. Để được hoàn toàn rõ ràng, xóa từ bảng này là tích cực khuyến khích; nó là "tùy chọn hạt nhân" và người dùng không bị ảo tưởng ngược lại. Tuy nhiên, đôi khi nó phải được thực hiện.
Giản đồ trông giống như sau:
Widgets
|
+--- Anvils [1:1]
| |
| +--- AnvilTestData [1:N]
|
+--- WidgetHistory (1:N)
|
+--- WidgetHistoryDetails (1:N)
định nghĩa cột trông giống như sau:
Widgets (WidgetID int PK, WidgetName varchar(50))
Anvils (AnvilID int PK, WidgetID int FK/IX/UNIQUE, ...)
AnvilTestData (AnvilID int FK/IX, TestID int, ...Test Data...)
WidgetHistory (HistoryID int PK, WidgetID int FK/IX, HistoryDate datetime, ...)
WidgetHistoryDetails (HistoryID int FK/IX, DetailType smallint, ...)
Không có gì quá đáng sợ, thực sự. Widget
có thể là các loại khác nhau, một loại Anvil
là một loại đặc biệt, do đó mối quan hệ là 1: 1 (hoặc chính xác hơn 1: 0..1). Sau đó, có một lượng lớn dữ liệu - có lẽ hàng nghìn hàng AnvilTestData
mỗi Anvil
được thu thập theo thời gian, xử lý độ cứng, ăn mòn, trọng lượng chính xác, khả năng tương thích của búa, các vấn đề khả năng sử dụng và thử nghiệm tác động với đầu hoạt hình.
Sau đó, mỗi Widget
có lịch sử lâu dài, nhàm chán về nhiều loại giao dịch - sản xuất, di chuyển hàng tồn kho, bán hàng, điều tra lỗi, RMA, sửa chữa, khiếu nại của khách hàng, v.v. hoặc không có gì cả, tùy thuộc vào độ tuổi của nó.
Vì vậy, không ngạc nhiên, có mối quan hệ CASCADE DELETE
ở mọi cấp ở đây. Nếu cần xóa Widget
, điều đó có nghĩa là đã xảy ra lỗi và chúng tôi cần xóa mọi bản ghi của tiện ích con đó, bao gồm lịch sử, dữ liệu thử nghiệm, v.v. Một lần nữa, tùy chọn hạt nhân.
Quan hệ tất cả được lập chỉ mục, thống kê được cập nhật. Truy vấn bình thường rất nhanh. Hệ thống có xu hướng hum dọc theo khá trơn tru cho tất cả mọi thứ ngoại trừ xóa.
Bắt đến điểm ở đây, cuối cùng, vì nhiều lý do chúng tôi chỉ cho phép xóa một widget ở một thời gian, do đó, một tuyên bố xóa sẽ trông như thế này:
DELETE FROM Widgets
WHERE WidgetID = @WidgetID
Khá đơn giản, vô thưởng vô phạt tìm xóa ... mất hơn 2 phút để chạy, cho tiện ích con có không có dữ liệu!
Sau khi slogging thông qua kế hoạch thực hiện tôi cuối cùng đã có thể chọn ra các AnvilTestData
và WidgetHistoryDetails
xóa dưới dạng các hoạt động phụ có chi phí cao nhất. Vì vậy, tôi đã thử nghiệm với tắt CASCADE
(nhưng giữ FK thực tế, chỉ cần đặt nó vào NO ACTION
) và viết lại kịch bản như một cái gì đó rất giống với những điều sau đây:
DECLARE @AnvilID int
SELECT @AnvilID = AnvilID FROM Anvils WHERE WidgetID = @WidgetID
DELETE FROM AnvilTestData
WHERE AnvilID = @AnvilID
DELETE FROM WidgetHistory
WHERE HistoryID IN (
SELECT HistoryID
FROM WidgetHistory
WHERE WidgetID = @WidgetID)
DELETE FROM Widgets WHERE WidgetID = @WidgetID
Cả hai "tối ưu hóa" dẫn đến sự tăng tốc đáng kể , mỗi lần cạo mất gần một phút trong khoảng thời gian thực hiện, do đó việc xóa 2 phút gốc mất khoảng 5-10 giây - ít nhất là đối với mới tiện ích con, không có nhiều dữ liệu kiểm tra hoặc lịch sử.
Chỉ cần để được hoàn toàn rõ ràng, vẫn còn một CASCADE
WidgetHistory
-WidgetHistoryDetails
, nơi fanout là cao nhất, tôi chỉ loại bỏ một trong những nguồn gốc từ Widgets
.
Tiếp tục "làm phẳng" của mối quan hệ thác dẫn đến dần dần ít kịch tính nhưng vẫn speedups đáng chú ý, đến mức xóa mới phụ tùng gần như ngay lập tức một khi tất cả các thác xóa các bảng lớn hơn đã được gỡ bỏ và thay thế bằng rõ ràng xóa.
Tôi đang sử dụng DBCC DROPCLEANBUFFERS
và DBCC FREEPROCCACHE
trước mỗi lần kiểm tra. Tôi đã vô hiệu hóa tất cả các trình kích hoạt có thể gây chậm hơn nữa (mặc dù các trình kích hoạt đó sẽ hiển thị trong kế hoạch thực thi). Và tôi đang thử nghiệm đối với các tiện ích cũ hơn và cũng chú ý đến một tốc độ đáng kể ở đó; xóa mà sử dụng để mất 5 phút bây giờ mất 20-40 giây.
Bây giờ tôi là một người ủng hộ nhiệt tình của triết lý "CHỌN không bị hỏng", nhưng dường như không có bất kỳ lời giải thích hợp lý nào cho hành vi này ngoài việc nghiền nát, không hiệu quả trong quan hệ CASCADE DELETE
.
Vì vậy, câu hỏi của tôi là:
Đây có phải là một vấn đề được biết đến với DRI trong SQL Server? (Tôi không thể tìm thấy bất kỳ tham chiếu nào về loại điều này trên Google hoặc tại đây trong SO; Tôi nghi ngờ câu trả lời là không.)
Nếu không, có giải thích khác cho hành vi tôi không nhìn thấy?
Nếu đó là vấn đề đã biết, tại sao lại là sự cố và có cách giải quyết tốt hơn tôi có thể đang sử dụng không?
Giản đồ? U chắc chắn không phải là một chỉ số mất tích đơn giản về phía N của FKs? –
@Remus: Ví dụ về lược đồ ở đó (nếu có bất kỳ chi tiết nào bị thiếu, hãy cho tôi biết những gì bạn muốn xem). Chắc chắn, 100% tích cực nó không phải là một chỉ số mất tích (ngay cả khi nó được, sau đó phiên bản thứ hai sẽ được làm chậm quá, phải không?) – Aaronaught
Thêm vào một số cột/index/FK định nghĩa trong trường hợp giúp ở tất cả. – Aaronaught