Tại sao điều này nhanh hơn và nó an toàn để sử dụng? (Chữ cái đầu tiên ở đâu trong bảng chữ cái)


10

Tóm lại, chúng tôi đang cập nhật những bảng người nhỏ với các giá trị từ một bảng người rất lớn. Trong một thử nghiệm gần đây, bản cập nhật này mất khoảng 5 phút để chạy.

Chúng tôi tình cờ phát hiện ra thứ tối ưu nhất có thể, có vẻ như hoạt động hoàn hảo! Truy vấn tương tự bây giờ chạy trong chưa đầy 2 phút và tạo ra kết quả tương tự, hoàn hảo.

Đây là truy vấn. Dòng cuối cùng được thêm vào là "tối ưu hóa". Tại sao thời gian truy vấn giảm mạnh? Có phải chúng ta đang thiếu một cái gì đó? Điều này có thể dẫn đến các vấn đề trong tương lai?

UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
    AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')

Ghi chú kỹ thuật: Chúng tôi biết rằng danh sách các chữ cái để kiểm tra có thể cần thêm một vài chữ cái. Chúng tôi cũng nhận thức được lỗi rõ ràng khi sử dụng "KHÁC BIỆT".

Gói truy vấn (thông thường): https://www.brentozar.com/pastetheplan/?id=rypV84y7V
Gói truy vấn (có "tối ưu hóa"): https://www.brentozar.com/pastetheplan/?id=r1aC2my7E


4
Trả lời nhỏ cho ghi chú kỹ thuật của bạn: AND LEFT(TRIM(largeTbl.last_name), 1) BETWEEN 'a' AND 'z' COLLATE LATIN1_GENERAL_CI_AInên làm những gì bạn muốn ở đó mà không yêu cầu bạn liệt kê tất cả các ký tự và có mã khó đọc
Erik A

Bạn có hàng trong đó điều kiện cuối cùng trong WHERElà sai? Đặc biệt lưu ý rằng so sánh có thể là trường hợp nhạy cảm.
jpmc26

@ErikvonAsmuth làm cho một điểm tuyệt vời. Nhưng, chỉ là một lưu ý kỹ thuật nhỏ: đối với SQL Server 2008 và 2008 R2, tốt nhất nên sử dụng phiên bản "100" collations (nếu có sẵn cho văn hóa / ngôn ngữ được sử dụng). Vì vậy, đó sẽ là Latin1_General_100_CI_AI. Và đối với SQL Server 2012 trở lên (thông qua ít nhất là SQL Server 2019), tốt nhất nên sử dụng các đối chiếu hỗ trợ Ký tự bổ sung trong phiên bản cao nhất cho ngôn ngữ được sử dụng. Vì vậy, đó sẽ là Latin1_General_100_CI_AI_SCtrong trường hợp này. Phiên bản> 100 (chỉ có tiếng Nhật cho đến nay) không có (hoặc cần) _SC(ví dụ Japanese_XJIS_140_CI_AI).
Solomon Rutzky

Câu trả lời:


9

Nó phụ thuộc vào dữ liệu trong bảng, chỉ mục của bạn, .... Khó có thể nói mà không thể so sánh các kế hoạch thực hiện / thống kê thời gian io +.

Sự khác biệt tôi mong đợi là việc lọc thêm xảy ra trước THAM GIA giữa hai bảng. Trong ví dụ của tôi, tôi đã thay đổi các bản cập nhật thành các lựa chọn để sử dụng lại các bảng của mình.

Kế hoạch thực hiện với "tối ưu hóa" nhập mô tả hình ảnh ở đây

Kế hoạch thực hiện

Bạn thấy rõ một hoạt động của bộ lọc xảy ra, trong dữ liệu thử nghiệm của tôi không có bản ghi nào được lọc ra và kết quả là không có cải tiến nào được thực hiện.

Kế hoạch thực hiện, không có "tối ưu hóa" nhập mô tả hình ảnh ở đây

Kế hoạch thực hiện

Bộ lọc không còn nữa, điều đó có nghĩa là chúng ta sẽ phải dựa vào phép nối để lọc ra các bản ghi không cần thiết.

(Các) lý do khác Một lý do / hậu quả khác của việc thay đổi truy vấn có thể là, một kế hoạch thực hiện mới đã được tạo khi thay đổi truy vấn, diễn ra nhanh hơn. Một ví dụ về điều này là công cụ chọn một toán tử Tham gia khác, nhưng đó chỉ là phỏng đoán tại thời điểm này.

BIÊN TẬP:

Làm rõ sau khi nhận được hai kế hoạch truy vấn:

Truy vấn đang đọc 550M Hàng từ bảng lớn và lọc chúng ra. nhập mô tả hình ảnh ở đây

Có nghĩa là vị ngữ là phần tử thực hiện hầu hết các bộ lọc, không phải là vị từ tìm kiếm. Kết quả là dữ liệu được đọc, nhưng cách ít được trả lại.

Làm cho máy chủ sql sử dụng một chỉ mục khác (kế hoạch truy vấn) / thêm một chỉ mục có thể giải quyết điều này.

Vậy tại sao truy vấn tối ưu hóa không có vấn đề tương tự?

Bởi vì một kế hoạch truy vấn khác nhau được sử dụng, với một lần quét thay vì tìm kiếm.

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Không thực hiện bất kỳ tìm kiếm nào, mà chỉ trả về 4M hàng để làm việc.

Sự khác biệt tiếp theo

Bỏ qua sự khác biệt cập nhật (không có gì được cập nhật trên truy vấn được tối ưu hóa), một kết quả băm được sử dụng trên truy vấn được tối ưu hóa:

nhập mô tả hình ảnh ở đây

Thay vì một vòng lặp lồng nhau tham gia vào không được tối ưu hóa:

nhập mô tả hình ảnh ở đây

Một vòng lặp lồng nhau là tốt nhất khi một bảng nhỏ và một bảng khác lớn. Vì cả hai đều có cùng kích thước, tôi cho rằng khớp băm là lựa chọn tốt hơn trong trường hợp này.

Tổng quat

Truy vấn tối ưu hóa nhập mô tả hình ảnh ở đây

Kế hoạch của truy vấn được tối ưu hóa có tính tương đồng, sử dụng phép nối khớp băm và cần thực hiện lọc IO ít hơn. Nó cũng sử dụng một bitmap để loại bỏ các giá trị chính không thể tạo ra bất kỳ hàng tham gia nào. (Ngoài ra không có gì đang được cập nhật)

Truy vấn nhập mô tả hình ảnh ở đây không được tối ưu hóa Kế hoạch của truy vấn không được tối ưu hóa không có tính tương đồng, sử dụng phép nối vòng lặp lồng nhau và cần thực hiện lọc IO dư trên các bản ghi 550M. (Ngoài ra bản cập nhật đang diễn ra)

Bạn có thể làm gì để cải thiện truy vấn không được tối ưu hóa?

  • Thay đổi chỉ mục để có First_name & last_name trong danh sách cột chính:

    TẠO INDEX IX_largeTableOfP People_birth_date_first_name_last_name trên dbo.largeTableOfP People (birthday_date, First_name, last_name) bao gồm (id)

Nhưng do việc sử dụng các hàm và bảng này lớn nên đây có thể không phải là giải pháp tối ưu.

  • Cập nhật số liệu thống kê, sử dụng biên dịch lại để thử và có được kế hoạch tốt hơn.
  • Thêm TÙY CHỌN (HASH JOIN, MERGE JOIN)vào truy vấn
  • ...

Kiểm tra dữ liệu + Truy vấn được sử dụng

CREATE TABLE #smallTableOfPeople(importantValue int, birthDate datetime2, first_name varchar(50),last_name varchar(50));
CREATE TABLE #largeTableOfPeople(importantValue int, birth_date datetime2, first_name varchar(50),last_name varchar(50));


set nocount on;
DECLARE @i int = 1
WHILE @i <= 1000
BEGIN
insert into #smallTableOfPeople (importantValue,birthDate,first_name,last_name)
VALUES(NULL, dateadd(mi,@i,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @i += 1;
END


set nocount on;
DECLARE @j int = 1
WHILE @j <= 20000
BEGIN
insert into #largeTableOfPeople (importantValue,birth_Date,first_name,last_name)
VALUES(@j, dateadd(mi,@j,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @j += 1;
END


SET STATISTICS IO, TIME ON;

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å');

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
--AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')




drop table #largeTableOfPeople;
drop table #smallTableOfPeople;

8

Không rõ ràng rằng truy vấn thứ hai trong thực tế là một cải tiến.

Các kế hoạch thực hiện chứa QueryTimeStats cho thấy sự khác biệt ít kịch tính hơn nhiều so với được nêu trong câu hỏi.

Kế hoạch chậm có thời gian trôi qua 257,556 ms(4 phút 17 giây). Kế hoạch nhanh có thời gian trôi qua 190,992 ms(3 phút 11 giây) mặc dù chạy với mức độ song song là 3.

Ngoài ra, kế hoạch thứ hai đã được chạy trong cơ sở dữ liệu nơi không có việc phải làm sau khi tham gia.

Kế hoạch đầu tiên

nhập mô tả hình ảnh ở đây

Kế hoạch thứ hai

nhập mô tả hình ảnh ở đây

Vì vậy, thời gian thêm có thể được giải thích bằng công việc cần thiết để cập nhật 3,5 triệu hàng (công việc cần có trong toán tử cập nhật để xác định các hàng này, chốt trang, viết cập nhật lên trang và nhật ký giao dịch không đáng kể)

Nếu điều này thực tế có thể tái tạo khi so sánh like với like thì lời giải thích là bạn vừa gặp may mắn trong trường hợp này.

Bộ lọc với 37 INđiều kiện chỉ loại bỏ 51 hàng trong số 4,008.334 trong bảng nhưng trình tối ưu hóa cho rằng nó sẽ loại bỏ nhiều hơn

nhập mô tả hình ảnh ở đây

   LEFT(TRIM(largeTbl.last_name), 1) IN ( 'a', 'à', 'á', 'b',
                                          'c', 'd', 'e', 'è',
                                          'é', 'f', 'g', 'h',
                                          'i', 'j', 'k', 'l',
                                          'm', 'n', 'o', 'ô',
                                          'ö', 'p', 'q', 'r',
                                          's', 't', 'u', 'ü',
                                          'v', 'w', 'x', 'y',
                                          'z', 'æ', 'ä', 'ø', 'å' ) 

Ước tính cardinality không chính xác như vậy thường là một điều xấu. Trong trường hợp này, nó tạo ra một kế hoạch có hình dạng khác (và song song) mà rõ ràng (?) Hoạt động tốt hơn cho bạn mặc dù sự cố tràn băm gây ra bởi sự đánh giá thấp.

Nếu không có TRIMSQL Server, có thể chuyển đổi điều này thành một khoảng phạm vi trong biểu đồ cột cơ sở và đưa ra các ước tính chính xác hơn nhiều nhưng với TRIMnó chỉ là dự đoán.

Bản chất của dự đoán có thể khác nhau nhưng ước tính cho một vị từ duy nhất LEFT(TRIM(largeTbl.last_name), 1)là trong một số trường hợp * chỉ ước tính được table_cardinality/estimated_number_of_distinct_column_values.

Tôi không chắc chính xác hoàn cảnh nào - kích thước của dữ liệu dường như đóng một phần. Tôi đã có thể tái tạo điều này với các kiểu dữ liệu có độ dài cố định rộng như ở đây nhưng có một dự đoán khác, cao hơn varchar(chỉ sử dụng tỷ lệ đoán 10% phẳng và ước tính 100.000 hàng). @Solomon Rutzky chỉ ra rằng nếu varchar(100)được đệm bằng các khoảng trống theo sau cho charước tính thấp hơn được sử dụng

Các INdanh sách được mở rộng ra ORvà SQL Server sử dụng backoff mũ với tối đa là 4 vị xem xét. Vì vậy, 219.707ước tính được đưa ra như sau.

DECLARE @TableCardinality FLOAT = 4008334, 
        @DistinctColumnValueEstimate FLOAT = 34207

DECLARE @NotSelectivity float = 1 - (1/@DistinctColumnValueEstimate)

SELECT @TableCardinality * ( 1 - (
@NotSelectivity * 
SQRT(@NotSelectivity) * 
SQRT(SQRT(@NotSelectivity)) * 
SQRT(SQRT(SQRT(@NotSelectivity)))
))
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.