Câu trả lời của tôi sẽ tập trung hầu hết vào SQL Server chỉ vì tôi sẽ đưa ra câu trả lời khá chi tiết và tôi không có cùng trình độ chuyên môn trong các nền tảng khác.
Trước tiên, điều quan trọng là phải nhận ra rằng trình tối ưu hóa truy vấn không hoạt động trực tiếp với SQL bạn viết. Nó được chuyển đổi thành một định dạng nội bộ trước khi tối ưu hóa. Những gì bạn đã liệt kê cho truy vấn tiềm năng 1 và truy vấn 2 là khá giống nhau, ngoại trừ một quan điểm khác biệt tinh tế. Một câu hỏi tương tự về sự khác biệt giữa truy vấn 1 và truy vấn 2 đã được hỏi và trả lời ở đây . Nếu bạn muốn tìm hiểu thêm về định dạng nội bộ mà SQL Server sử dụng, bạn có thể xem qua một loạt bài đăng blog tuyệt vời của Paul White . Tuy nhiên, hầu hết thời gian chỉ cần so sánh các gói truy vấn của hai truy vấn mà bạn nghi ngờ có thể được tối ưu hóa theo cùng một cách.
Có một số cách sử dụng chế độ xem có thể cải thiện hiệu suất:
Có thể xác định các khung nhìn được triển khai dưới dạng cấu trúc vật lý trên cơ sở dữ liệu. Trong SQL Server, chúng được gọi là các khung nhìn được lập chỉ mục . Trong Oracle, chúng được gọi là các khung nhìn cụ thể hóa. Trong Oracle, một truy vấn không được viết theo quan điểm cụ thể hóa vẫn có thể sử dụng chế độ xem cụ thể hóa. Thảo luận thêm nằm ngoài phạm vi của câu trả lời này.
Đôi khi cùng một truy vấn SQL cần chạy trên nhiều nền tảng RDBMS. Với chế độ xem, chúng tôi có thể sử dụng cú pháp duy nhất cho mỗi nền tảng nhưng cùng một truy vấn được gửi đến cơ sở dữ liệu. Nếu không có chế độ xem, chúng ta có thể phải sử dụng các hàm do người dùng xác định có thể có hại cho hiệu suất.
Đôi khi mọi người đặt mã thực sự thông minh trong quan điểm. Nếu nó tốt hơn những gì bạn đã viết, bạn có thể cải thiện hiệu suất bằng cách sử dụng chế độ xem.
Nói chung, một truy vấn được viết theo quan điểm sẽ hiệu quả hoặc kém hiệu quả hơn một truy vấn được viết trực tiếp trên các bảng cơ sở. Điều này là do định nghĩa chế độ xem thường chứa các cột và phép nối bổ sung có thể không cần thiết cho câu hỏi cụ thể mà truy vấn đối với chế độ xem đang hỏi. Để có được hiệu suất tốt trước một quan điểm phức tạp, chúng tôi hy vọng rằng có ba điều xảy ra:
Loại bỏ cột. Nếu một cột được trình bày trong chế độ xem nhưng không được đề cập trong truy vấn đối với chế độ xem thì giá trị không được tính.
Tham gia loại bỏ. Nếu một bảng được sử dụng trong một khung nhìn có thể được loại bỏ một cách an toàn mà không thay đổi kết quả thì nó nên được loại bỏ. Đôi khi điều này có thể xảy ra nếu trình tối ưu hóa có thêm thông tin. Ví dụ, khóa ngoại có thể không được khai báo trong cơ sở dữ liệu. Mặt khác, quy tắc để thực hiện loại bỏ tham gia có thể không bao gồm một kịch bản nhất định. Ví dụ, trong loại bỏ tham gia Oracle không thể xảy ra đối với tham gia nhiều cột nhưng có thể trong SQL Server.
Dự đoán đẩy xuống. Nếu tôi thêm bộ lọc vào chế độ xem và có một chỉ mục trên cột bên dưới thì tôi có thể sử dụng chỉ mục đó. Tôi tin rằng đây là những gì ví dụ của bạn là gợi ý. Ngay cả khi không có chỉ mục, tôi vẫn muốn các bộ lọc được đẩy xuống càng xa càng tốt trong kế hoạch để tránh những công việc không cần thiết.
Theo kinh nghiệm của tôi, các quy tắc này được triển khai khá tốt bởi trình tối ưu hóa truy vấn, điều này tất nhiên là một điều tốt, nhưng nó có thể xấu cho các bản demo SE. Tuy nhiên, nếu chúng ta viết mã lén lút, chúng ta có thể kết thúc bằng các ví dụ cho thấy tất cả các tối ưu hóa ở trên đều thất bại. Điều này là do các quy tắc thực hiện tối ưu hóa không được thiết kế để bao gồm mọi tình huống có thể xảy ra.
Đầu tiên tôi sẽ tạo một số dữ liệu mẫu đơn giản. Dữ liệu không quan trọng lắm, nhưng các định nghĩa bảng là.
DROP TABLE IF EXISTS dbo.BASE_TABLE;
CREATE TABLE dbo.BASE_TABLE (
ID INT NOT NULL,
ID2 INT NOT NULL,
FILLER VARCHAR(50),
CONSTRAINT BASE_TABLE_ID CHECK (ID > 0),
PRIMARY KEY (ID)
);
INSERT INTO dbo.BASE_TABLE WITH (TABLOCK)
SELECT TOP (1000000)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
DROP TABLE IF EXISTS dbo.EXTRA_TABLE;
CREATE TABLE dbo.EXTRA_TABLE (
ID INT NOT NULL,
ID2 INT NOT NULL,
FILLER VARCHAR(50),
PRIMARY KEY (ID, ID2)
);
INSERT INTO dbo.EXTRA_TABLE WITH (TABLOCK)
SELECT TOP (1000000)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
DROP TABLE IF EXISTS dbo.EMPTY_TABLE;
CREATE TABLE dbo.EMPTY_TABLE(
ID INT NOT NULL,
PRIMARY KEY (ID)
);
DROP TABLE IF EXISTS dbo.EMPTY_CCI;
CREATE TABLE dbo.EMPTY_CCI (
ID INT NOT NULL
, INDEX CCI_EMPTY_CCI CLUSTERED COLUMNSTORE
);
GO
CREATE FUNCTION dbo.THE_BEST_FUNCTION () RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
GO
Đây là định nghĩa quan điểm lén lút của tôi:
CREATE VIEW dbo.SNEAKY_VIEW
AS
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
, dbo.THE_BEST_FUNCTION() FUNCTION_VALUE
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
LEFT OUTER JOIN dbo.EMPTY_CCI ON 1 = 0
LEFT OUTER JOIN dbo.EMPTY_TABLE e2 ON t.ID = e2.ID;
GO
Quan điểm không may là một mớ hỗn độn. Là một trình tối ưu hóa của con người, có thể đơn giản hóa truy vấn đó một chút. Việc kết hợp chống lại EXTRA_TABLE
có thể được loại bỏ vì chúng tôi tham gia với khóa chính đầy đủ để số lượng hàng sẽ không thay đổi. Bất kỳ hàng nào cũng không thể khớp, nhưng đó là một liên kết ngoài nên sẽ không có gì bị loại bỏ. Việc tham gia chống lại EMPTY_CCI
có thể được loại bỏ vì điều kiện tham gia sẽ không bao giờ khớp. Việc tham gia chống lại EMPTY_TABLE
có thể được loại bỏ bởi vì chúng tôi tham gia với khóa chính đầy đủ. Hàm cũng luôn trả về NULL
nên không cần đưa vào đó. Vì vậy, chúng ta có thể đơn giản hóa việc này:
SELECT
ID
, 1 CNT
, NULL
FROM dbo.BASE_TABLE t;
Tuy nhiên, chúng ta có thể làm tốt hơn thế. ID
luôn luôn tích cực do các ràng buộc và ID
luôn luôn là duy nhất bởi vì đó là khóa chính. Vì vậy, COUNT
chức năng cửa sổ sẽ luôn là 1. Truy vấn có thể được viết lại như thế này:
SELECT
ID
, 1 CNT
, NULL
FROM dbo.BASE_TABLE t;
Trình tối ưu hóa truy vấn có thể đơn giản hóa một truy vấn theo quan điểm đó không? Hãy tìm hiểu bằng cách so sánh các kế hoạch. Đây là kế hoạch cho truy vấn đơn giản:
Đây là kế hoạch SELECT *
chống lại quan điểm:
Chúng khá khác nhau một chút. Chúng ta hãy đi qua các truy vấn ví dụ khác để xem các ví dụ về tối ưu hóa ở trên có lẽ không hoạt động chính xác như mong đợi.
Đầu tiên, các hàm do người dùng xác định vô hướng có hại cho hiệu năng trong SQL Server. Trong số các vấn đề khác, họ buộc toàn bộ truy vấn phải có kế hoạch nối tiếp. Truy vấn này đủ điều kiện cho một kế hoạch song song và tôi nhận được một trong máy của mình:
SELECT *
FROM (
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
LEFT OUTER JOIN dbo.EMPTY_CCI ON 1 = 0
LEFT OUTER JOIN dbo.EMPTY_TABLE e2 ON t.ID = e2.ID
) derived_table;
Kế hoạch:
Tuy nhiên, ngay cả khi tôi không chọn cột dựa trên chức năng do người dùng xác định trong chế độ xem, tôi vẫn nhận được một gói nối tiếp bắt buộc:
SELECT ID, CNT
FROM dbo.SNEAKY_VIEW;
Vì vậy, có một sự khác biệt giữa việc sử dụng khung nhìn và bảng dẫn xuất. Đôi khi việc loại bỏ cột sẽ không hoạt động theo cùng một cách.
Đối với ví dụ thứ hai, EMPTY_CCI
bảng sẽ không ảnh hưởng đến kết quả của truy vấn, vì vậy hãy xóa nó khỏi bảng dẫn xuất:
SELECT *
FROM (
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
LEFT OUTER JOIN dbo.EMPTY_TABLE e2 ON t.ID = e2.ID
) derived_table;
Đây là kế hoạch truy vấn:
Tuy nhiên, có một sự khác biệt trong kế hoạch truy vấn so với quan điểm:
SELECT ID, CNT, FUNCTION_VALUE
FROM dbo.SNEAKY_VIEW;
Chế độ xem có thể sử dụng chế độ hàng loạt , đây là một cách đặc biệt để thực hiện thực hiện truy vấn khi các chỉ mục của cửa hàng cột được tham gia vào một truy vấn. Mặc dù EMPTY_CCI
bảng không hiển thị trong kế hoạch, truy vấn vẫn đủ điều kiện cho chế độ hàng loạt. Lưu ý rằng EXTRA_TABLE
không cần thiết truy vấn trong cả hai truy vấn. Đó là vì điều kiện nối quá phức tạp đối với SQL Server để xác định rằng phép nối đó an toàn để loại bỏ. Cũng lưu ý rằng EMPTY_TABLE
bảng không hiển thị trong cả hai kế hoạch truy vấn. Trình tối ưu hóa truy vấn đã có thể loại bỏ nó trong cả hai truy vấn.
Đối với ví dụ thứ ba, chúng ta hãy xem xét đẩy lùi vị ngữ. Giả sử tôi muốn lọc chỉ bao gồm các hàng ở đâu ID = 500000
. Nếu tôi làm điều đó phần nào trực tiếp bên ngoài quan điểm:
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
WHERE ABS(t.ID) = 500000
OPTION (MAXDOP 1);
Tôi không thể sử dụng chỉ số chống lại BASE_TABLE.ID
do ABS()
chức năng. Tuy nhiên, bộ lọc được đẩy xuống quét và chỉ có một hàng được mong đợi được trả về. Điều đó có thể cải thiện hiệu suất:
Với truy vấn này:
SELECT ID, CNT
FROM dbo.SNEAKY_VIEW
WHERE ID = 500000;
Chúng tôi nhận được một kế hoạch ít hiệu quả hơn. Bộ lọc trên ID = 500000
được triển khai ở cuối kế hoạch, do đó, chức năng cửa sổ sẽ được đánh giá dựa trên gần một triệu hàng không cần thiết:
Đó có thể là một chút lặn sâu hơn những gì bạn đang tìm kiếm. Quay trở lại câu hỏi ban đầu của bạn, tôi sẽ nói rằng truy vấn đối với chế độ xem tương tự như truy vấn tiềm năng 1. Tôi đã nghe thấy những tin đồn liên quan đến một số trường hợp không đúng. Ví dụ: được cho là nếu bạn có nhiều chế độ xem lồng nhau thì trình tối ưu hóa truy vấn có thể gặp khó khăn khi giải nén chúng và bạn có thể kết thúc với một truy vấn được tối ưu hóa kém chỉ vì lý do đó. Tôi không biết điều đó có đúng trong phiên bản SQL Server hiện tại hay không nhưng nên tránh đơn giản để những người khác cố gắng hiểu mã của bạn có thời gian dễ dàng hơn với nó.