Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Các nguyên tắc cơ bản về biểu thức bảng, Phần 12 - Hàm nội tuyến được định giá trong bảng

Bài viết này là phần thứ mười hai trong loạt bài về các biểu thức bảng được đặt tên. Cho đến nay, tôi đã đề cập đến các bảng và CTE dẫn xuất, là các biểu thức bảng được đặt tên trong phạm vi câu lệnh và các khung nhìn, là các biểu thức bảng được đặt tên có thể sử dụng lại. Tháng này, tôi giới thiệu các hàm có giá trị bảng nội tuyến hoặc iTVF và mô tả lợi ích của chúng so với các biểu thức bảng được đặt tên khác. Tôi cũng so sánh chúng với các thủ tục được lưu trữ, chủ yếu tập trung vào sự khác biệt về chiến lược tối ưu hóa mặc định và lập kế hoạch cho hành vi sử dụng và lưu vào bộ nhớ đệm. Có rất nhiều điều cần đề cập về mặt tối ưu hóa, vì vậy tôi sẽ bắt đầu thảo luận trong tháng này và tiếp tục vào tháng sau.

Trong các ví dụ của mình, tôi sẽ sử dụng cơ sở dữ liệu mẫu có tên là TSQLV5. Bạn có thể tìm thấy tập lệnh tạo và điền nó tại đây và sơ đồ ER của nó tại đây.

Hàm Inline Table-Valued là gì?

So với các biểu thức bảng được đặt tên được đề cập trước đó, iTVF hầu hết giống với các chế độ xem. Giống như các khung nhìn, iTVF được tạo như một đối tượng cố định trong cơ sở dữ liệu và do đó người dùng có quyền tương tác với chúng có thể sử dụng lại. Lợi thế chính của iTVF so với lượt xem là thực tế là chúng hỗ trợ các tham số đầu vào. Vì vậy, cách dễ nhất để mô tả iTVF là một dạng xem được tham số hóa, mặc dù về mặt kỹ thuật, bạn tạo nó bằng câu lệnh CREATE FUNCTION chứ không phải bằng câu lệnh CREATE VIEW.

Điều quan trọng là không nhầm lẫn iTVF với các hàm có giá trị bảng nhiều câu lệnh (MSTVF). Trước đây là một biểu thức bảng được đặt tên có thể nhập được dựa trên một truy vấn duy nhất tương tự như một dạng xem và là trọng tâm của bài viết này. Sau đó là một mô-đun lập trình trả về một biến bảng làm đầu ra của nó, với luồng nhiều câu lệnh trong nội dung của nó với mục đích là điền dữ liệu vào biến bảng được trả về.

Cú pháp

Đây là cú pháp T-SQL để tạo iTVF:

TẠO CHỨC NĂNG [HOẶC ALTER] [. ]

[()]

BẢNG QUAY LẠI

[WITH ]

NHƯ

QUAY LẠI

[; ]

Quan sát trong cú pháp khả năng xác định các tham số đầu vào.

Mục đích của thuộc tính SCHEMABIDNING cũng giống như với các chế độ xem và nên được đánh giá dựa trên các cân nhắc tương tự. Để biết chi tiết, hãy xem Phần 10 của loạt bài này.

Một ví dụ

Ví dụ về iTVF, giả sử bạn cần tạo một biểu thức bảng được đặt tên có thể sử dụng lại chấp nhận làm đầu vào ID khách hàng (@custid) và một số (@n) và trả về số lượng đơn đặt hàng gần đây nhất được yêu cầu từ bảng Bán hàng. cho khách hàng đầu vào.

Bạn không thể triển khai tác vụ này với một chế độ xem vì các chế độ xem thiếu hỗ trợ cho các tham số đầu vào. Như đã đề cập, bạn có thể coi iTVF như một chế độ xem được tham số hóa và như vậy, nó là công cụ phù hợp cho nhiệm vụ này.

Trước khi triển khai chính chức năng, đây là mã để tạo chỉ mục hỗ trợ trên bảng Bán hàng. Đơn hàng:

USE TSQLV5;
GO
 
CREATE INDEX idx_nc_cid_odD_oidD_i_eid
  ON Sales.Orders(custid, orderdate DESC, orderid DESC)
  INCLUDE(empid);

Và đây là mã để tạo hàm, có tên là Sales.GetTopCustOrders:

CREATE OR ALTER FUNCTION Sales.GetTopCustOrders
  ( @custid AS INT, @n AS BIGINT )
RETURNS TABLE
AS
RETURN
  SELECT TOP (@n) orderid, orderdate, empid
  FROM Sales.Orders
  WHERE custid = @custid
  ORDER BY orderdate DESC, orderid DESC;
GO

Cũng giống như với các bảng và chế độ xem cơ sở, khi sau khi truy xuất dữ liệu, bạn chỉ định iTVF trong mệnh đề FROM của câu lệnh SELECT. Dưới đây là một ví dụ yêu cầu ba đơn đặt hàng gần đây nhất cho khách hàng 1:

SELECT orderid, orderdate, empid
FROM Sales.GetTopCustOrders(1, 3);

Tôi sẽ gọi ví dụ này là Truy vấn 1. Kế hoạch cho Truy vấn 1 được thể hiện trong Hình 1.

Hình 1:Kế hoạch cho Truy vấn 1

Nội dung giới thiệu về iTVF là gì?

Nếu bạn đang thắc mắc về nguồn của thuật ngữ nội tuyến trong các hàm có giá trị bảng nội tuyến, nó liên quan đến cách chúng được tối ưu hóa. Khái niệm nội tuyến có thể áp dụng cho tất cả bốn loại biểu thức bảng được đặt tên mà T-SQL hỗ trợ, và một phần liên quan đến những gì tôi đã mô tả trong Phần 4 của loạt bài này là hủy ghi chú / thay thế. Đảm bảo rằng bạn truy cập lại phần có liên quan trong Phần 4 nếu bạn cần cập nhật.

Như bạn có thể thấy trong Hình 1, nhờ thực tế là hàm được nội tuyến, SQL Server có thể tạo một kế hoạch tối ưu tương tác trực tiếp với các chỉ mục của bảng cơ sở bên dưới. Trong trường hợp của chúng tôi, kế hoạch thực hiện tìm kiếm trong chỉ mục hỗ trợ mà bạn đã tạo trước đó.

iTVF đưa khái niệm nội tuyến lên một bước xa hơn bằng cách áp dụng tối ưu hóa nhúng tham số theo mặc định. Paul White mô tả việc tối ưu hóa nhúng tham số trong bài viết xuất sắc của anh ấy. Tham số Sniffing, Embedding, and the RECOMPILE Options. Với tối ưu hóa nhúng tham số, các tham chiếu tham số truy vấn được thay thế bằng các giá trị hằng số theo nghĩa đen từ quá trình thực thi hiện tại và sau đó mã có hằng số được tối ưu hóa.

Quan sát trong sơ đồ trong Hình 1 rằng cả vị từ tìm kiếm của toán tử Tìm kiếm chỉ mục và biểu thức hàng đầu của toán tử Top đều hiển thị các giá trị hằng số 1 và 3 được nhúng từ việc thực thi truy vấn hiện tại. Chúng không hiển thị các tham số @custid và @n tương ứng.

Với iTVFs, tối ưu hóa nhúng tham số được sử dụng theo mặc định. Với các thủ tục được lưu trữ, các truy vấn được tham số hóa được tối ưu hóa theo mặc định. Bạn cần thêm TÙY CHỌN (RECOMPILE) vào truy vấn của thủ tục được lưu trữ để yêu cầu tối ưu hóa nhúng tham số. Thông tin chi tiết về việc tối ưu hóa iTVF so với các thủ tục được lưu trữ, bao gồm các hàm ý, sẽ sớm có.

Sửa đổi dữ liệu thông qua iTVFs

Nhớ lại phần 11 của loạt bài này rằng miễn là đáp ứng các yêu cầu nhất định, các biểu thức bảng được đặt tên có thể là mục tiêu của các câu lệnh sửa đổi. Khả năng này áp dụng cho iTVF tương tự như cách nó áp dụng cho lượt xem. Ví dụ:đây là mã bạn có thể sử dụng để xóa ba đơn đặt hàng gần đây nhất của khách hàng 1 (không thực sự chạy mã này):

DELETE FROM Sales.GetTopCustOrders(1, 3);

Cụ thể trong cơ sở dữ liệu của chúng tôi, cố gắng chạy mã này sẽ không thành công do thực thi tính toàn vẹn tham chiếu (các đơn hàng bị ảnh hưởng xảy ra có các dòng đơn hàng liên quan trong bảng Sales.OrderDetails), nhưng đó là mã hợp lệ và được hỗ trợ.

iTVFs so với Quy trình được lưu trữ

Như đã đề cập trước đó, chiến lược tối ưu hóa truy vấn mặc định cho iTVF khác với chiến lược dành cho các thủ tục được lưu trữ. Với iTVFs, mặc định là sử dụng tối ưu hóa nhúng tham số. Với các thủ tục được lưu trữ, mặc định là tối ưu hóa các truy vấn được tham số hóa trong khi áp dụng tính năng dò tìm tham số. Để nhúng tham số cho một truy vấn thủ tục được lưu trữ, bạn cần thêm TÙY CHỌN (RECOMPILE).

Như với nhiều chiến lược và kỹ thuật tối ưu hóa, nhúng tham số có những điểm cộng và điểm hạn chế.

Điểm cộng chính là nó cho phép đơn giản hóa truy vấn mà đôi khi có thể dẫn đến các kế hoạch hiệu quả hơn. Một số đơn giản hóa đó thực sự hấp dẫn. Paul chứng minh điều này bằng các quy trình được lưu trữ trong bài viết của anh ấy và tôi sẽ chứng minh điều này với iTVF vào tháng tới.

Điểm trừ chính của tối ưu hóa nhúng thông số là bạn không có được hành vi lưu trữ và sử dụng lại kế hoạch hiệu quả như cách bạn làm đối với các kế hoạch được tham số hóa. Với mỗi sự kết hợp riêng biệt của các giá trị tham số, bạn sẽ nhận được một chuỗi truy vấn riêng biệt và do đó, một biên dịch riêng biệt dẫn đến một kế hoạch được lưu trong bộ nhớ cache riêng biệt. Với iTVF có đầu vào không đổi, bạn có thể nhận được hành vi sử dụng lại theo kế hoạch, nhưng chỉ khi các giá trị tham số giống nhau được lặp lại. Rõ ràng, một truy vấn thủ tục được lưu trữ với OPTION (RECOMPILE) sẽ không sử dụng lại một kế hoạch ngay cả khi lặp lại các giá trị tham số giống nhau, theo yêu cầu.

Tôi sẽ chứng minh ba trường hợp:

  1. Các kế hoạch có thể tái sử dụng với các hằng số là kết quả từ việc tối ưu hoá nhúng tham số mặc định cho các truy vấn iTVF có hằng số
  2. Các kế hoạch được tham số hóa có thể sử dụng lại do tối ưu hóa mặc định của các truy vấn thủ tục được lưu trữ được tham số hóa
  3. Các kế hoạch không thể sử dụng với các hằng số do tối ưu hóa nhúng tham số cho các truy vấn thủ tục được lưu trữ với TÙY CHỌN (RECOMPILE)

Hãy bắt đầu với trường hợp số 1.

Sử dụng mã sau để truy vấn iTVF của chúng tôi với @custid =1 và @n =3:

SELECT orderid, orderdate, empid
FROM Sales.GetTopCustOrders(1, 3);

Xin nhắc lại, đây sẽ là lần thực thi thứ hai của cùng một mã vì bạn đã thực thi nó một lần với cùng các giá trị tham số trước đó, dẫn đến kế hoạch được hiển thị trong Hình 1.

Sử dụng mã sau để truy vấn iTVF với @custid =2 và @n =3 một lần:

SELECT orderid, orderdate, empid
FROM Sales.GetTopCustOrders(2, 3);

Tôi sẽ gọi đoạn mã này là Truy vấn 2. Kế hoạch cho Truy vấn 2 được thể hiện trong Hình 2.

Hình 2:Kế hoạch cho Truy vấn 2

Nhớ lại rằng kế hoạch trong Hình 1 cho Truy vấn 1 đề cập đến ID khách hàng không đổi 1 trong vị từ tìm kiếm, trong khi kế hoạch này đề cập đến ID khách hàng không đổi 2.

Sử dụng mã sau để kiểm tra thống kê thực thi truy vấn:

SELECT Q.plan_handle, Q.execution_count, T.text, P.query_plan
FROM sys.dm_exec_query_stats AS Q
  CROSS APPLY sys.dm_exec_sql_text(Q.plan_handle) AS T
  CROSS APPLY sys.dm_exec_query_plan(Q.plan_handle) AS P
WHERE T.text LIKE '%Sales.' + 'GetTopCustOrders(%';

Mã này tạo ra kết quả sau:

plan_handle         execution_count text                                           query_plan
------------------- --------------- ---------------------------------------------- ----------------
0x06000B00FD9A1...  1               SELECT ... FROM Sales.GetTopCustOrders(2, 3);  <ShowPlanXML...>
0x06000B00F5C34...  2               SELECT ... FROM Sales.GetTopCustOrders(1, 3);  <ShowPlanXML...>

(2 rows affected)

Có hai kế hoạch riêng biệt được tạo ở đây:một cho truy vấn có ID khách hàng 1, được sử dụng hai lần và một kế hoạch khác cho truy vấn với ID khách hàng 2, được sử dụng một lần. Với một số lượng lớn các kết hợp riêng biệt của các giá trị tham số, bạn sẽ kết thúc với một số lượng lớn các tập hợp và kế hoạch được lưu trong bộ nhớ cache.

Hãy tiếp tục với trường hợp số 2:chiến lược tối ưu hóa mặc định của các truy vấn thủ tục được lưu trữ được tham số hóa. Sử dụng mã sau để đóng gói truy vấn của chúng tôi trong một thủ tục được lưu trữ có tên là Sales.GetTopCustOrders2:

CREATE OR ALTER PROC Sales.GetTopCustOrders2
  ( @custid AS INT, @n AS BIGINT )
AS
  SET NOCOUNT ON;
 
  SELECT TOP (@n) orderid, orderdate, empid
  FROM Sales.Orders
  WHERE custid = @custid
  ORDER BY orderdate DESC, orderid DESC;
GO

Sử dụng đoạn mã sau để thực thi quy trình đã lưu trữ với @custid =1 và @n =3 hai lần:

EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;
EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;

Lần thực thi đầu tiên kích hoạt việc tối ưu hóa truy vấn, dẫn đến kế hoạch được tham số hóa được hiển thị trong Hình 3:

Hình 3:Kế hoạch bán hàng.GetTopCustOrders2 proc

Quan sát tham chiếu đến tham số @custid trong vị từ tìm kiếm và tham số @n trong biểu thức trên cùng.

Sử dụng mã sau để thực thi quy trình đã lưu trữ với @custid =2 và @n =3 một lần:

EXEC Sales.GetTopCustOrders2 @custid = 2, @n = 3;

Kế hoạch tham số được lưu trong bộ nhớ cache được hiển thị trong Hình 3 được sử dụng lại một lần nữa.

Sử dụng mã sau để kiểm tra thống kê thực thi truy vấn:

SELECT Q.plan_handle, Q.execution_count, T.text, P.query_plan
FROM sys.dm_exec_query_stats AS Q
  CROSS APPLY sys.dm_exec_sql_text(Q.plan_handle) AS T
  CROSS APPLY sys.dm_exec_query_plan(Q.plan_handle) AS P
WHERE T.text LIKE '%Sales.' + 'GetTopCustOrders2%';

Mã này tạo ra kết quả sau:

plan_handle         execution_count text                                            query_plan
------------------- --------------- ----------------------------------------------- ----------------
0x05000B00F1604...  3               ...SELECT TOP (@n)...WHERE custid = @custid...; <ShowPlanXML...>

(1 row affected)

Chỉ một gói được tham số hóa đã được tạo và lưu vào bộ nhớ đệm, đồng thời được sử dụng ba lần, bất chấp các giá trị ID khách hàng thay đổi.

Hãy tiếp tục với trường hợp số 3. Như đã đề cập, với các truy vấn thủ tục được lưu trữ, bạn có thể nhận được tối ưu hóa nhúng tham số khi sử dụng TÙY CHỌN (RECOMPILE). Sử dụng mã sau để thay đổi truy vấn thủ tục để bao gồm tùy chọn này:

CREATE OR ALTER PROC Sales.GetTopCustOrders2
  ( @custid AS INT, @n AS BIGINT )
AS
  SET NOCOUNT ON;
 
  SELECT TOP (@n) orderid, orderdate, empid
  FROM Sales.Orders
  WHERE custid = @custid
  ORDER BY orderdate DESC, orderid DESC
  OPTION(RECOMPILE);
GO

Thực thi proc với @custid =1 và @n =3 hai lần:

EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;
EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;

Bạn nhận được cùng một kế hoạch được hiển thị trước đó trong Hình 1 với các hằng số được nhúng.

Thực thi proc với @custid =2 và @n =3 một lần:

EXEC Sales.GetTopCustOrders2 @custid = 2, @n = 3;

Bạn nhận được cùng một kế hoạch được hiển thị trước đó trong Hình 2 với các hằng số được nhúng.

Kiểm tra thống kê thực thi truy vấn:

SELECT Q.plan_handle, Q.execution_count, T.text, P.query_plan
FROM sys.dm_exec_query_stats AS Q
  CROSS APPLY sys.dm_exec_sql_text(Q.plan_handle) AS T
  CROSS APPLY sys.dm_exec_query_plan(Q.plan_handle) AS P
WHERE T.text LIKE '%Sales.' + 'GetTopCustOrders2%';

Mã này tạo ra kết quả sau:

plan_handle         execution_count text                                            query_plan
------------------- --------------- ----------------------------------------------- ----------------
0x05000B00F1604...  1               ...SELECT TOP (@n)...WHERE custid = @custid...; <ShowPlanXML...>

(1 row affected)

Số lần thực hiện hiển thị 1, chỉ phản ánh lần thực hiện cuối cùng. SQL Server lưu trữ kế hoạch được thực thi gần đây nhất, vì vậy nó có thể hiển thị số liệu thống kê cho lần thực thi đó, nhưng theo yêu cầu, nó không sử dụng lại kế hoạch đó. Nếu bạn kiểm tra kế hoạch được hiển thị trong thuộc tính query_plan, bạn sẽ thấy đó là kế hoạch được tạo cho các hằng số trong lần thực thi cuối cùng, được hiển thị trước đó trong Hình 2.

Nếu bạn đang thực hiện ít lần tổng hợp hơn và hành vi sử dụng lại và lưu vào bộ nhớ đệm của kế hoạch hiệu quả, thì cách tiếp cận tối ưu hóa thủ tục được lưu trữ mặc định của các truy vấn được tham số hóa là cách để thực hiện.

Có một lợi thế lớn mà triển khai dựa trên iTVF có so với triển khai dựa trên thủ tục được lưu trữ — khi bạn cần áp dụng hàm cho từng hàng trong bảng và chuyển các cột từ bảng làm đầu vào. Ví dụ:giả sử bạn cần trả lại ba đơn đặt hàng gần đây nhất cho mỗi khách hàng trong bảng Sales.Customers. Không có cấu trúc truy vấn nào cho phép bạn áp dụng một thủ tục được lưu trữ trên mỗi hàng trong bảng. Bạn có thể triển khai một giải pháp lặp lại với con trỏ, nhưng đây luôn là một ngày tốt lành khi bạn có thể tránh con trỏ. Kết hợp nhà điều hành APPLICY với cuộc gọi iTVF, bạn có thể hoàn thành nhiệm vụ một cách tốt đẹp và rõ ràng, như vậy:

SELECT C.custid, O.orderid, O.orderdate, O.empid
FROM Sales.Customers AS C
  CROSS APPLY Sales.GetTopCustOrders( C.custid, 3 ) AS O;

Mã này tạo ra kết quả sau (viết tắt):

custid      orderid     orderdate  empid
----------- ----------- ---------- -----------
1           11011       2019-04-09 3
1           10952       2019-03-16 1
1           10835       2019-01-15 1
2           10926       2019-03-04 4
2           10759       2018-11-28 3
2           10625       2018-08-08 3
...

(263 rows affected)

Lời gọi hàm được nội tuyến và tham chiếu đến tham số @custid được thay thế bằng C.custid tương quan. Điều này dẫn đến kế hoạch thể hiện trong Hình 4.

Hình 4:Lập kế hoạch cho truy vấn với APPLICY và Sales.GetTopCustOrders iTVF

Kế hoạch quét một số chỉ mục trên bảng Sales.Customers để lấy tập hợp các ID khách hàng và áp dụng tìm kiếm trong chỉ mục hỗ trợ mà bạn đã tạo trước đó trên Sales.Orders cho mỗi khách hàng. Chỉ có một kế hoạch vì hàm được đưa vào trong truy vấn bên ngoài, biến thành một phép nối tương quan hoặc một bên. Kế hoạch này có hiệu quả cao, đặc biệt khi cột custid trong Bán hàng. Đơn đặt hàng rất dày đặc, nghĩa là khi có một số lượng nhỏ ID khách hàng riêng biệt.

Tất nhiên, có những cách khác để triển khai tác vụ này, chẳng hạn như sử dụng CTE với hàm ROW_NUMBER. Giải pháp như vậy có xu hướng hoạt động tốt hơn giải pháp dựa trên ÁP DỤNG khi cột custid trong bảng Đơn đặt hàng có mật độ thấp. Dù bằng cách nào, nhiệm vụ cụ thể mà tôi đã sử dụng trong các ví dụ của mình không quá quan trọng đối với mục đích của cuộc thảo luận của chúng ta. Ý của tôi là giải thích các chiến lược tối ưu hóa khác nhau mà SQL Server sử dụng với các công cụ khác nhau.

Khi bạn hoàn tất, hãy sử dụng mã sau để dọn dẹp:

DROP INDEX IF EXISTS idx_nc_cid_odD_oidD_i_eid ON Sales.Orders;

Tóm tắt và Điều gì tiếp theo

Vậy, chúng ta đã học được gì từ điều này?

ITVF là một biểu thức bảng có tên được tham số hóa có thể sử dụng lại.

SQL Server sử dụng chiến lược tối ưu hóa nhúng tham số với iTVF theo mặc định và chiến lược tối ưu hóa truy vấn được tham số hóa với các truy vấn thủ tục được lưu trữ. Thêm TÙY CHỌN (RECOMPILE) vào truy vấn thủ tục được lưu trữ có thể dẫn đến tối ưu hóa nhúng tham số.

Nếu bạn muốn nhận được ít lần biên dịch hơn và hành vi sử dụng lại và lưu vào bộ nhớ đệm của kế hoạch hiệu quả, kế hoạch truy vấn thủ tục được tham số hóa là cách để thực hiện.

Các kế hoạch cho truy vấn iTVF được lưu vào bộ nhớ cache và có thể được sử dụng lại, miễn là các giá trị tham số giống nhau được lặp lại.

Bạn có thể kết hợp thuận tiện việc sử dụng toán tử ÁP DỤNG và iTVF để áp dụng iTVF cho mỗi hàng từ bảng bên trái, chuyển các cột từ bảng bên trái làm đầu vào cho iTVF.

Như đã đề cập, có rất nhiều điều cần đề cập về tối ưu hóa iTVF. Trong tháng này, tôi đã so sánh các iTVF và các thủ tục được lưu trữ về chiến lược tối ưu hóa mặc định và lập kế hoạch hành vi lưu vào bộ nhớ đệm và tái sử dụng. Tháng tới, tôi sẽ tìm hiểu sâu hơn về các đơn giản hóa do tối ưu hóa nhúng tham số.


No
  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Hiểu về phân tích dữ liệu lớn

  2. Câu lệnh SQL DROP TABLE và các trường hợp sử dụng khác nhau

  3. So sánh các phương pháp tách / nối chuỗi

  4. Thông báo về tính khả dụng chung của SQL Safe Backup 8.7.2

  5. Cải thiện Giải pháp Đánh số Trung vị Hàng