Bạn không nên phụ thuộc quá nhiều vào tỷ lệ phần trăm chi phí trong kế hoạch thực hiện. Đây luôn là chi phí ước tính , ngay cả trong các kế hoạch hậu thực hiện với số 'thực tế' cho những thứ như số hàng. Các chi phí ước tính dựa trên một mô hình hoạt động khá tốt cho mục đích mà nó dự định: cho phép trình tối ưu hóa lựa chọn giữa các kế hoạch thực hiện ứng viên khác nhau cho cùng một truy vấn. Thông tin chi phí rất thú vị và là một yếu tố cần xem xét, nhưng hiếm khi nó phải là một số liệu chính để điều chỉnh truy vấn. Giải thích thông tin kế hoạch thực hiện đòi hỏi một cái nhìn rộng hơn về dữ liệu được trình bày.
Toán tử tìm kiếm mục cụm cụm ItemTran
Toán tử này thực sự là hai hoạt động trong một. Đầu tiên một hoạt động tìm kiếm chỉ mục tìm thấy tất cả các hàng khớp với vị từ product_code_v42 = 'M10BOLT'
, sau đó mỗi hàng có vị từ còn lại bit_field_v41 & 4 = 0
được áp dụng. Có một chuyển đổi ngầm định bit_field_v41
từ loại cơ sở của nó ( tinyint
hoặc smallint
) sang integer
.
Việc chuyển đổi xảy ra do toán tử bit-AND (&) yêu cầu cả hai toán hạng phải cùng loại. Kiểu ẩn của giá trị không đổi '4' là số nguyên và quy tắc ưu tiên loại dữ liệu có nghĩa là bit_field_v41
giá trị trường ưu tiên thấp hơn được chuyển đổi.
Vấn đề (chẳng hạn như) có thể dễ dàng sửa chữa bằng cách viết biến vị ngữ là bit_field_v41 & CONVERT(tinyint, 4) = 0
- có nghĩa là giá trị không đổi có mức ưu tiên thấp hơn và được chuyển đổi (trong quá trình gấp không đổi) thay vì giá trị cột. Nếu bit_field_v41
là tinyint
không có chuyển đổi diễn ra ở tất cả. Tương tự như vậy, CONVERT(smallint, 4)
có thể được sử dụng nếu bit_field_v41
có smallint
. Điều đó nói rằng, chuyển đổi không phải là một vấn đề hiệu suất trong trường hợp này, nhưng vẫn nên thực hiện tốt để khớp các loại và tránh chuyển đổi ngầm nếu có thể.
Phần chính của chi phí ước tính cho tìm kiếm này là theo kích thước của bảng cơ sở. Trong khi khóa chỉ mục được phân cụm là hẹp một cách hợp lý, kích thước của mỗi hàng là lớn. Một định nghĩa cho bảng không được đưa ra, nhưng chỉ các cột được sử dụng trong dạng xem thêm vào chiều rộng hàng đáng kể. Vì chỉ mục được nhóm bao gồm tất cả các cột, khoảng cách giữa các khóa chỉ mục được phân cụm là chiều rộng của hàng , không phải chiều rộng của các khóa chỉ mục . Việc sử dụng hậu tố phiên bản trên một số cột cho thấy bảng thực thậm chí còn có nhiều cột hơn cho các phiên bản trước.
Nhìn vào các cột tìm kiếm, vị ngữ dư và đầu ra, hiệu năng của toán tử này có thể được kiểm tra riêng rẽ bằng cách xây dựng truy vấn tương đương (đây 1 <> 2
là một mẹo để ngăn chặn tham số tự động, mâu thuẫn được loại bỏ bởi trình tối ưu hóa và không xuất hiện trong kế hoạch truy vấn):
SELECT
it.booking_no_v32,
it.QtyCheckedOut,
it.QtyReturned,
it.Trans_qty,
it.trans_type_v41
FROM dbo.tblItemTran AS it
WHERE
1 <> 2
AND it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0;
Hiệu năng của truy vấn này với bộ đệm dữ liệu lạnh rất đáng quan tâm, vì việc đọc trước sẽ bị ảnh hưởng bởi sự phân mảnh bảng (chỉ mục cụm). Khóa phân cụm cho bảng này mời phân mảnh, do đó, điều quan trọng là phải duy trì (sắp xếp lại hoặc xây dựng lại) chỉ mục này thường xuyên và sử dụng một khoảng thích hợp FILLFACTOR
để cho phép không gian cho các hàng mới giữa các cửa sổ bảo trì chỉ mục.
Tôi đã thực hiện một thử nghiệm về tác động của phân mảnh đối với việc đọc trước bằng cách sử dụng dữ liệu mẫu được tạo bằng SQL Data Generator . Sử dụng cùng một hàng trong bảng được tính như trong kế hoạch truy vấn của câu hỏi, một chỉ mục được phân đoạn cao dẫn đến SELECT * FROM view
mất 15 giây sau DBCC DROPCLEANBUFFERS
. Thử nghiệm tương tự trong cùng điều kiện với chỉ mục cụm được xây dựng lại mới trên bảng ItemTrans hoàn thành sau 3 giây.
Nếu dữ liệu bảng thường hoàn toàn trong bộ đệm, thì vấn đề phân mảnh là rất ít quan trọng. Nhưng, ngay cả với độ phân mảnh thấp, các hàng của bảng rộng có thể có nghĩa là số lần đọc logic và vật lý cao hơn nhiều so với dự kiến. Bạn cũng có thể thử nghiệm thêm và xóa phần rõ ràng CONVERT
để xác thực sự mong đợi của tôi rằng vấn đề chuyển đổi ngầm định không quan trọng ở đây, ngoại trừ vi phạm thực tiễn tốt nhất.
Thêm vào đó là số lượng hàng ước tính rời khỏi toán tử tìm kiếm. Ước tính thời gian tối ưu hóa là 165 hàng, nhưng 4.226 được tạo ra tại thời điểm thực hiện. Tôi sẽ trở lại điểm này sau, nhưng lý do chính cho sự khác biệt là do tính chọn lọc của vị từ còn lại (liên quan đến bitwise-AND) rất khó để trình tối ưu hóa dự đoán - thực tế là nó phải đoán.
Toán tử lọc
Tôi đang hiển thị vị từ bộ lọc ở đây chủ yếu để minh họa cách hai NOT IN
danh sách được kết hợp, đơn giản hóa và sau đó được mở rộng và cũng để cung cấp một tham chiếu cho cuộc thảo luận khớp băm sau đây. Truy vấn kiểm tra từ tìm kiếm có thể được mở rộng để kết hợp các hiệu ứng của nó và xác định ảnh hưởng của toán tử Bộ lọc đối với hiệu suất:
SELECT
it.booking_no_v32,
it.trans_type_v41,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut
FROM dbo.tblItemTran AS it
WHERE
it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND
(
(
it.trans_type_v41 NOT IN (2, 3, 6, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
)
);
Toán tử tính toán vô hướng trong kế hoạch xác định biểu thức sau (chính phép tính được hoãn lại cho đến khi kết quả được yêu cầu bởi toán tử sau):
[Expr1016] = (trans_qty - (QtyCheckedOut - QtyReturned))
Toán tử kết hợp Hash
Việc thực hiện nối trên các kiểu dữ liệu ký tự không phải là lý do cho chi phí ước tính cao của toán tử này. Chú giải công cụ SSMS chỉ hiển thị mục nhập Hash Keys thăm dò, nhưng các chi tiết quan trọng nằm trong cửa sổ Thuộc tính SSMS.
Toán tử Hash Match xây dựng bảng băm bằng cách sử dụng các giá trị của booking_no_v32
cột (Hash Keys Build) từ bảng ItemTran, sau đó thăm dò các kết quả khớp bằng booking_no
cột (Đầu dò Hash Hash) từ bảng Đặt chỗ. Chú giải công cụ SSMS cũng thường hiển thị Đầu dò dư, nhưng văn bản quá dài đối với một chú giải công cụ và đơn giản là bị bỏ qua.
Một phần dư thăm dò tương tự như phần dư được thấy sau khi chỉ số tìm kiếm trước đó; vị từ còn lại được ước tính trên tất cả các hàng khớp với hàm băm để xác định xem hàng có được truyền cho toán tử cha hay không. Tìm kết quả băm trong bảng băm cân bằng là cực kỳ nhanh, nhưng việc áp dụng một vị từ dư phức tạp cho mỗi hàng khớp với nhau là khá chậm. Chú giải công cụ Hash Match trong Plan Explorer hiển thị chi tiết, bao gồm cả biểu thức thăm dò dư:
Vị từ còn lại rất phức tạp và bao gồm kiểm tra trạng thái tiến trình đặt phòng ngay bây giờ cột đó có sẵn từ bảng đặt chỗ. Chú giải công cụ cũng cho thấy sự khác biệt tương tự giữa số lượng hàng ước tính và thực tế được thấy trước đó trong tìm kiếm chỉ mục. Có vẻ kỳ lạ khi phần lớn quá trình lọc được thực hiện hai lần, nhưng đây chỉ là trình tối ưu hóa lạc quan. Người ta không mong đợi các phần của bộ lọc có thể được đẩy xuống kế hoạch từ phần dư của đầu dò để loại bỏ bất kỳ hàng nào (ước tính số hàng giống nhau trước và sau bộ lọc) nhưng trình tối ưu hóa biết rằng nó có thể sai về điều đó. Cơ hội lọc các hàng sớm (giảm chi phí của phép nối băm) xứng đáng với chi phí nhỏ của bộ lọc bổ sung. Toàn bộ bộ lọc không thể được đẩy xuống vì nó bao gồm một bài kiểm tra trên một cột từ bảng đặt chỗ, nhưng hầu hết có thể.
Số lượng hàng bị đánh giá thấp là một vấn đề đối với toán tử Hash Match vì số lượng bộ nhớ dành cho bảng băm dựa trên số lượng hàng ước tính. Trong trường hợp bộ nhớ quá nhỏ so với kích thước của bảng băm cần thiết trong thời gian chạy (do số lượng hàng lớn hơn), bảng băm đệ quy tràn vào bộ lưu trữ tempdb vật lý , thường dẫn đến hiệu suất rất kém. Trong trường hợp xấu nhất, công cụ thực thi dừng đệ quy các thùng băm và khu nghỉ mát đến mức rất chậmthuật toán giải cứu. Tràn băm (đệ quy hoặc giải cứu) là nguyên nhân rất có thể của các vấn đề về hiệu suất được nêu trong câu hỏi (không phải là cột tham gia kiểu ký tự hoặc chuyển đổi ngầm). Nguyên nhân gốc sẽ là do máy chủ dự trữ quá ít bộ nhớ cho truy vấn dựa trên ước tính số lượng hàng (cardinality) không chính xác.
Đáng buồn thay, trước SQL Server 2012, không có dấu hiệu nào trong kế hoạch thực hiện rằng hoạt động băm vượt quá phân bổ bộ nhớ của nó (không thể tăng động sau khi được bảo lưu trước khi bắt đầu thực thi, ngay cả khi máy chủ có khối lượng bộ nhớ trống) và phải tràn vào tempdb. Có thể theo dõi Lớp Sự kiện Cảnh báo Hash bằng Profiler, nhưng có thể khó tương quan các cảnh báo với một truy vấn cụ thể.
Sửa lỗi
Ba vấn đề là phân mảnh, phần còn lại của đầu dò phức tạp trong toán tử khớp băm và ước tính số lượng thẻ không chính xác dẫn đến việc đoán tại chỉ số tìm kiếm.
Giải pháp được đề nghị
Kiểm tra sự phân mảnh và sửa nó nếu cần thiết, lên lịch bảo trì để đảm bảo chỉ số được tổ chức chấp nhận được. Cách thông thường để điều chỉnh ước tính cardinality là cung cấp số liệu thống kê. Trong trường hợp này, trình tối ưu hóa cần số liệu thống kê cho sự kết hợp ( product_code_v42
, bitfield_v41 & 4 = 0
). Chúng ta không thể tạo số liệu thống kê trực tiếp trên một biểu thức, vì vậy trước tiên chúng ta phải tạo một cột được tính cho biểu thức trường bit, sau đó tạo số liệu thống kê nhiều cột thủ công:
ALTER TABLE dbo.tblItemTran
ADD Bit3 AS bit_field_v41 & CONVERT(tinyint, 4);
CREATE STATISTICS [stats dbo.ItemTran (product_code_v42, Bit3)]
ON dbo.tblItemTran (product_code_v42, Bit3);
Định nghĩa văn bản cột được tính toán phải khớp chính xác với văn bản trong định nghĩa chế độ xem chính xác cho các số liệu thống kê được sử dụng, do đó, việc sửa chế độ xem để loại bỏ chuyển đổi ngầm phải được thực hiện cùng lúc và phải cẩn thận để đảm bảo khớp văn bản.
Các thống kê nhiều cột phải dẫn đến các ước tính tốt hơn nhiều, làm giảm đáng kể khả năng toán tử khớp băm sẽ sử dụng phương pháp đổ đệ quy hoặc thuật toán giải cứu. Thêm cột được tính toán (là hoạt động chỉ siêu dữ liệu và không có khoảng trống trong bảng vì nó không được đánh dấu PERSISTED
) và thống kê nhiều cột là phỏng đoán tốt nhất của tôi ở giải pháp đầu tiên.
Khi giải quyết các vấn đề về hiệu năng truy vấn, điều quan trọng là phải đo lường những thứ như thời gian trôi qua, sử dụng CPU, đọc logic, đọc vật lý, loại chờ và thời lượng ... vv. Cũng có thể hữu ích khi chạy các phần của truy vấn một cách riêng biệt để xác thực các nguyên nhân nghi ngờ như được hiển thị ở trên.
Trong một số môi trường, trong đó chế độ xem dữ liệu lên đến từng giây không quan trọng, việc chạy một quy trình nền cụ thể hóa toàn bộ chế độ xem thành bảng chụp nhanh thường xuyên có thể hữu ích. Bảng này chỉ là một bảng cơ sở bình thường và có thể được lập chỉ mục cho các truy vấn đọc mà không lo ảnh hưởng đến hiệu suất cập nhật.
Xem chỉ mục
Đừng cố gắng lập chỉ mục cho quan điểm ban đầu trực tiếp. Hiệu suất đọc sẽ nhanh đến mức đáng kinh ngạc (một lần tìm kiếm trên một chỉ mục xem) nhưng (trong trường hợp này), tất cả các vấn đề về hiệu năng trong các gói truy vấn hiện tại sẽ được chuyển sang các truy vấn sửa đổi bất kỳ cột trong bảng nào được tham chiếu trong dạng xem. Các truy vấn thay đổi các hàng của bảng cơ sở thực sự sẽ bị ảnh hưởng rất xấu.
Giải pháp nâng cao với chế độ xem được lập chỉ mục một phần
Có một giải pháp xem được lập chỉ mục một phần cho truy vấn cụ thể này để sửa các ước tính cardinality và loại bỏ bộ lọc và thăm dò dư, nhưng nó dựa trên một số giả định về dữ liệu (chủ yếu là phỏng đoán của tôi tại lược đồ) và yêu cầu thực hiện chuyên gia, đặc biệt phù hợp các chỉ mục để hỗ trợ các kế hoạch bảo trì xem chỉ mục. Tôi chia sẻ mã dưới đây để quan tâm, tôi không đề xuất bạn thực hiện nó mà không phân tích và kiểm tra rất cẩn thận .
-- Indexed view to optimize the main view
CREATE VIEW dbo.V1
WITH SCHEMABINDING
AS
SELECT
it.ID,
it.product_code_v42,
it.trans_type_v41,
it.booking_no_v32,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut,
it.QtyReserved,
it.bit_field_v41,
it.prep_on,
it.From_locn,
it.Trans_to_locn,
it.PDate,
it.FirstDate,
it.PTimeH,
it.PTimeM,
it.RetnDate,
it.BookDate,
it.TimeBookedH,
it.TimeBookedM,
it.TimeBookedS,
it.del_time_hour,
it.del_time_min,
it.return_to_locn,
it.return_time_hour,
it.return_time_min,
it.AssignTo,
it.AssignType,
it.InRack
FROM dbo.tblItemTran AS it
JOIN dbo.tblBookings AS tb ON
tb.booking_no = it.booking_no_v32
WHERE
(
it.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND tb.BookingProgressStatus = 1
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
);
GO
CREATE UNIQUE CLUSTERED INDEX cuq ON dbo.V1 (product_code_v42, ID);
GO
Chế độ xem hiện tại được điều chỉnh để sử dụng chế độ xem được lập chỉ mục ở trên:
CREATE VIEW [dbo].[vwReallySlowView2]
AS
SELECT
I.booking_no_v32 AS bkno,
I.trans_type_v41 AS trantype,
B.Assigned_to_v61 AS Assignbk,
B.order_date AS dateo,
B.HourBooked AS HBooked,
B.MinBooked AS MBooked,
B.SecBooked AS SBooked,
I.prep_on AS Pon,
I.From_locn AS Flocn,
I.Trans_to_locn AS TTlocn,
CASE I.prep_on
WHEN 'Y' THEN I.PDate
ELSE I.FirstDate
END AS PrDate,
I.PTimeH AS PrTimeH,
I.PTimeM AS PrTimeM,
CASE
WHEN I.RetnDate < I.FirstDate
THEN I.FirstDate
ELSE I.RetnDate
END AS RDatev,
I.bit_field_v41 AS bitField,
I.FirstDate AS FDatev,
I.BookDate AS DBooked,
I.TimeBookedH AS TBookH,
I.TimeBookedM AS TBookM,
I.TimeBookedS AS TBookS,
I.del_time_hour AS dth,
I.del_time_min AS dtm,
I.return_to_locn AS rtlocn,
I.return_time_hour AS rth,
I.return_time_min AS rtm,
CASE
WHEN
I.Trans_type_v41 IN (6, 7)
AND I.Trans_qty < I.QtyCheckedOut
THEN 0
WHEN
I.Trans_type_v41 IN (6, 7)
AND I.Trans_qty >= I.QtyCheckedOut
THEN I.Trans_Qty - I.QtyCheckedOut
ELSE
I.trans_qty
END AS trqty,
CASE
WHEN I.Trans_type_v41 IN (6, 7)
THEN 0
ELSE I.QtyCheckedOut
END AS MyQtycheckedout,
CASE
WHEN I.Trans_type_v41 IN (6, 7)
THEN 0
ELSE I.QtyReturned
END AS retqty,
I.ID,
B.BookingProgressStatus AS bkProg,
I.product_code_v42,
I.return_to_locn,
I.AssignTo,
I.AssignType,
I.QtyReserved,
B.DeprepOn,
CASE B.DeprepOn
WHEN 1 THEN B.DeprepDateTime
ELSE I.RetnDate
END AS DeprepDateTime,
I.InRack
FROM dbo.V1 AS I WITH (NOEXPAND)
JOIN dbo.tblbookings AS B ON
B.booking_no = I.booking_no_v32
JOIN dbo.tblInvmas AS M ON
I.product_code_v42 = M.product_code;
Ví dụ truy vấn và kế hoạch thực hiện:
SELECT
vrsv.*
FROM dbo.vwReallySlowView2 AS vrsv
WHERE vrsv.product_code_v42 = 'M10BOLT';
Trong kế hoạch mới, kết quả băm không có biến vị ngữ còn lại , không có bộ lọc phức tạp , không có biến vị ngữ còn lại trên chế độ xem được lập chỉ mục và ước tính chính xác là chính xác.
Như một ví dụ về cách các gói chèn / cập nhật / xóa sẽ bị ảnh hưởng, đây là kế hoạch chèn vào bảng ItemTrans:
Phần được tô sáng là mới và cần thiết để bảo trì chế độ xem được lập chỉ mục. Bộ đệm bảng phát lại các hàng của bảng cơ sở được chèn để bảo trì chế độ xem được lập chỉ mục. Mỗi hàng được nối với bảng đặt chỗ bằng cách tìm kiếm chỉ mục được nhóm, sau đó bộ lọc áp dụng các WHERE
vị từ mệnh đề phức tạp để xem liệu hàng đó có cần được thêm vào dạng xem không. Nếu vậy, một phần chèn được thực hiện theo chỉ mục được nhóm.
SELECT * FROM view
Thử nghiệm tương tự được thực hiện trước đó đã hoàn thành trong 150ms với chế độ xem được lập chỉ mục.
Điều cuối cùng: Tôi nhận thấy máy chủ 2008 R2 của bạn vẫn ở RTM. Nó sẽ không khắc phục được các vấn đề về hiệu suất của bạn, nhưng Gói dịch vụ 2 cho 2008 R2 đã có sẵn từ tháng 7 năm 2012 và có nhiều lý do chính đáng để duy trì hiện tại nhất có thể với các gói dịch vụ.