Database
 sql >> база данни >  >> RDS >> Database

Основи на табличните изрази, част 12 – Вградени функции с таблично стойности

Тази статия е дванадесетата част от поредица за изрази за именувани таблици. Досега обхванах извлечени таблици и CTE, които са изрази с именувани таблици с обхват на изрази, и изгледи, които са повторно използваеми изрази с именувани таблици. Този месец представям вградени функции с стойност на таблица или iTVF и описвам техните предимства в сравнение с другите наименовани таблични изрази. Също така ги сравнявам със съхранените процедури, като се фокусирам главно върху разликите по отношение на стратегията за оптимизация по подразбиране и планиране на поведението за кеширане и повторна употреба. Има много неща за разглеждане по отношение на оптимизацията, така че ще започна дискусията този месец и ще я продължа следващия месец.

В моите примери ще използвам примерна база данни, наречена TSQLV5. Можете да намерите скрипта, който го създава и попълва тук, и неговата ER диаграма тук.

Какво е вградена функция с таблично стойност?

В сравнение с по-рано обхванатите изрази за именувани таблици, iTVFs приличат предимно на изгледи. Подобно на изгледите, iTVF се създават като постоянен обект в базата данни и следователно могат да се използват повторно от потребители, които имат разрешения да взаимодействат с тях. Основното предимство на iTVF в сравнение с изгледите е фактът, че поддържат входни параметри. Така че, най-лесният начин да опишете iTVF е като параметризиран изглед, въпреки че технически го създавате с оператор CREATE FUNCTION, а не с оператор CREATE VIEW.

Важно е да не бъркате iTVF с функции с таблично стойности с множество оператори (MSTVF). Първият е вграден израз на именувана таблица, базиран на една заявка, подобна на изглед, и е фокусът на тази статия. Последният е програмен модул, който връща таблична променлива като свой изход, с поток от няколко оператора в тялото му, чиято цел е да запълни върнатата променлива на таблицата с данни.

Синтаксис

Ето синтаксиса на T-SQL за създаване на iTVF:

СЪЗДАЙТЕ [ИЛИ ПРОМЕНИ] ФУНКЦИЯ [<име на схема>. ] <име на функция>

[ (<входни параметри>) ]

ТАБЛИЦА ЗА ВРЪЩАНЕ

[ С <атрибути на функция, включително SCHEMABINDING> ]

КАТО

ВРЪЩАНЕ

<израз на таблица> [; ]

Спазвайте в синтаксиса възможността за дефиниране на входни параметри.

Целта на атрибута SCHEMABIDNING е същата като при изгледите и трябва да бъде оценена въз основа на подобни съображения. За подробности вижте част 10 от поредицата.

Пример

Като пример за iTVF, да предположим, че трябва да създадете повторно използваем израз на именувана таблица, който приема като входни идентификатор на клиента (@custid) и число (@n) и връща искания брой на най-новите поръчки от таблицата Sales.Orders за входния клиент.

Не можете да реализирате тази задача с изглед, тъй като изгледите нямат поддръжка за входни параметри. Както споменахме, можете да мислите за iTVF като за параметризиран изглед и като такъв той е правилният инструмент за тази задача.

Преди да приложите самата функция, ето код за създаване на поддържащ индекс в таблицата Sales.Orders:

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

А ето и кода за създаване на функцията, наречена 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

Точно както при базовите таблици и изгледи, когато след извличане на данни, вие посочвате iTVFs в клаузата FROM на оператор SELECT. Ето пример за заявяване на трите най-скорошни поръчки за клиент 1:

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

Ще се позова на този пример като заявка 1. Планът за заявка 1 е показан на фигура 1.

Фигура 1:План за заявка 1

Какво е вграденото за iTVF?

Ако се чудите какъв е източникът на термина inline във вградените функции с таблично стойности, това е свързано с начина, по който те се оптимизират. Концепцията за вграждане е приложима за всичките четири вида изрази на именувани таблици, които T-SQL поддържа, и отчасти включва това, което описах в част 4 от поредицата като разместване/заместване. Не забравяйте да прегледате отново съответния раздел в Част 4, ако имате нужда от освежаване.

Както можете да видите на фигура 1, благодарение на факта, че функцията е вградена, SQL Server успя да създаде оптимален план, който взаимодейства директно с индексите на основната таблица. В нашия случай планът извършва търсене в поддържащия индекс, който сте създали по-рано.

iTVF правят стъпка напред концепцията за вграждане, като прилагат оптимизация за вграждане на параметри по подразбиране. Пол Уайт описва оптимизацията за вграждане на параметри в отличната си статия Параметър Sniffing, Embedding, and the RECOMPILE Options. С оптимизацията за вграждане на параметри, препратките към параметрите на заявката се заменят с литералните константни стойности от текущото изпълнение и след това кодът с константите се оптимизира.

Забележете в плана на фигура 1, че както предикатът за търсене на оператора Index Seek, така и най-горният израз на оператора Top показват вградените литерални константни стойности 1 и 3 от текущото изпълнение на заявка. Те не показват съответно параметрите @custid и @n.

При iTVF оптимизацията за вграждане на параметри се използва по подразбиране. Със съхранените процедури параметризираните заявки се оптимизират по подразбиране. Трябва да добавите OPTION(RECOMPILE) към заявката на съхранена процедура, за да поискате оптимизация за вграждане на параметри. Повече подробности за оптимизирането на iTVFs спрямо съхранените процедури, включително последиците, скоро.

Промяна на данни чрез iTVFs

Припомнете си от част 11 от поредицата, че докато са изпълнени определени изисквания, изразите на именувани таблици могат да бъдат цел на изрази за модификация. Тази способност се прилага за iTVF, подобно на начина, по който се прилага към изгледите. Например, ето код, който можете да използвате, за да изтриете трите най-скорошни поръчки на клиент 1 (всъщност не стартирайте това):

DELETE FROM Sales.GetTopCustOrders(1, 3);

По-конкретно в нашата база данни, опитът за стартиране на този код би се провалил поради прилагане на референтната цялост (засегнатите поръчки имат свързани редове за поръчки в таблицата Sales.OrderDetails), но това е валиден и поддържан код.

iTVF срещу съхранени процедури

Както бе споменато по-рано, стратегията за оптимизиране на заявки по подразбиране за iTVFs е различна от тази за съхранените процедури. При iTVFs по подразбиране се използва оптимизация за вграждане на параметри. При съхранените процедури по подразбиране се оптимизират параметризирани заявки, докато се прилага подслушване на параметри. За да получите вграждане на параметри за заявка за съхранена процедура, трябва да добавите OPTION(RECOMPILE).

Както при много стратегии и техники за оптимизация, вграждането на параметри има своите плюсове и минуси.

Основният плюс е, че позволява опростяване на заявките, което понякога може да доведе до по-ефективни планове. Някои от тези опростявания са наистина завладяващи. Пол демонстрира това със съхранени процедури в статията си, а аз ще демонстрирам това с iTVF следващия месец.

Основният минус на оптимизацията за вграждане на параметри е, че не получавате ефективно кеширане на планове и поведение при повторно използване, както правите за параметризирани планове. С всяка отделна комбинация от стойности на параметрите получавате отделен низ на заявка и следователно отделна компилация, която води до отделен кеширан план. С iTVFs с постоянни входове можете да получите планово поведение при повторно използване, но само ако едни и същи стойности на параметрите се повтарят. Очевидно заявка за съхранена процедура с OPTION(RECOMPILE) няма да използва повторно план, дори когато се повтарят същите стойности на параметрите, по заявка.

Ще демонстрирам три случая:

  1. Планове за многократна употреба с константи в резултат на оптимизация за вграждане на параметри по подразбиране за iTVF заявки с константи
  2. Параметризирани планове за многократна употреба в резултат на оптимизация по подразбиране на заявки за параметризирани съхранени процедури
  3. Планове за многократна употреба с константи в резултат на оптимизиране на вграждане на параметри за заявки за съхранени процедури с OPTION(RECOMPILE)

Да започнем със случай №1.

Използвайте следния код, за да заявите нашия iTVF с @custid =1 и @n =3:

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

Като напомняне, това би било второто изпълнение на същия код, тъй като вече сте го изпълнили веднъж със същите стойности на параметрите по-рано, което води до плана, показан на фигура 1.

Използвайте следния код, за да направите заявка за iTVF с @custid =2 и @n =3 веднъж:

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

Ще наричам този код заявка 2. Планът за заявка 2 е показан на фигура 2.

Фигура 2:План за заявка 2

Припомнете си, че планът на фигура 1 за заявка 1 се отнася до постоянния клиентски идентификатор 1 в предиката за търсене, докато този план се отнася до постоянния клиентски идентификатор 2.

Използвайте следния код, за да разгледате статистиката за изпълнение на заявка:

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(%';
).

Този код генерира следния изход:

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)

Тук са създадени два отделни плана:един за заявката с клиентски идентификатор 1, който е използван два пъти, и друг за заявката с клиентски идентификатор 2, който е използван веднъж. С много голям брой различни комбинации от стойности на параметрите ще получите голям брой компилации и кеширани планове.

Нека продължим със случай №2:стратегията за оптимизация по подразбиране на параметризирани заявки за съхранени процедури. Използвайте следния код, за да капсулирате нашата заявка в съхранена процедура, наречена 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

Използвайте следния код, за да изпълните съхранената процедура с @custid =1 и @n =3 два пъти:

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

Първото изпълнение задейства оптимизирането на заявката, което води до параметризирания план, показан на Фигура 3:

Фигура 3:План за продажби.GetTopCustOrders2 proc

Наблюдавайте препратката към параметъра @custid в предиката за търсене и към параметъра @n в най-горния израз.

Използвайте следния код, за да изпълните съхранената процедура с @custid =2 и @n =3 веднъж:

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

Кешираният параметризиран план, показан на фигура 3, се използва отново.

Използвайте следния код, за да разгледате статистиката за изпълнение на заявка:

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%';

Този код генерира следния изход:

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

(1 row affected)

Само един параметризиран план беше създаден и кеширан и използван три пъти, въпреки променящите се стойности на клиентски идентификатор.

Да преминем към случай №3. Както споменахме, със заявки за съхранени процедури можете да получите оптимизация за вграждане на параметри, когато използвате OPTION(RECOMPILE). Използвайте следния код, за да промените заявката за процедура, за да включите тази опция:

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

Изпълнете процедурата с @custid =1 и @n =3 два пъти:

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

Получавате същия план, показан по-рано на Фигура 1 с вградените константи.

Изпълнете процедурата с @custid =2 и @n =3 веднъж:

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

Получавате същия план, показан по-рано на Фигура 2 с вградените константи.

Разгледайте статистиката за изпълнение на заявка:

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%';

Този код генерира следния изход:

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

(1 row affected)

Броят на изпълнението показва 1, отразявайки само последното изпълнение. SQL Server кешира последния изпълнен план, така че може да показва статистически данни за това изпълнение, но по заявка не използва повторно плана. Ако проверите плана, показан под атрибута query_plan, ще откриете, че е този, създаден за константите при последното изпълнение, показано по-рано на Фигура 2.

Ако търсите по-малко компилации и ефективно кеширане на планове и поведение при повторно използване, подходът за оптимизиране на запаметените процедури по подразбиране на параметризирани заявки е правилният начин.

Има голямо предимство, което една базирана на iTVF реализация има пред базирана на съхранени процедури – когато трябва да приложите функцията към всеки ред в таблицата и да предавате колони от таблицата като входни данни. Да предположим например, че трябва да върнете трите последни поръчки за всеки клиент в таблицата Sales.Customers. Никаква конструкция на заявка не ви позволява да приложите съхранена процедура на ред в таблица. Можете да приложите итеративно решение с курсор, но винаги е добър ден, когато можете да избегнете курсорите. Комбинирайки оператора APPLY с повикване iTVF, можете да постигнете задачата красиво и чисто, както следва:

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

Този код генерира следния изход (съкратено):

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)

Извикването на функцията става вградено и препратката към параметъра @custid се заменя с корелацията C.custid. Това води до плана, показан на Фигура 4.

Фигура 4:План за заявка с APPLY и Sales.GetTopCustOrders iTVF

Планът сканира някакъв индекс в таблицата Sales.Customers, за да получи набора от идентификатори на клиенти и прилага търсене в поддържащия индекс, който сте създали по-рано в Sales.Orders на клиент. Има само един план, тъй като функцията е вградена във външната заявка, превръщайки се в корелирано или странично присъединяване. Този план е високоефективен, особено когато custid колоната в Sales.Orders е много гъста, което означава, че има малък брой различни идентификационни номера на клиенти.

Разбира се, има и други начини за изпълнение на тази задача, като например използване на CTE с функцията ROW_NUMBER. Такова решение има тенденция да работи по-добре от базираното на APPLY, когато custid колоната в таблицата Sales.Orders е с ниска плътност. Така или иначе конкретната задача, която използвах в моите примери, не е толкова важна за целите на нашата дискусия. Целта ми беше да обясня различните стратегии за оптимизация, които SQL Server използва с различните инструменти.

Когато приключите, използвайте следния код за почистване:

DROP INDEX IF EXISTS idx_nc_cid_odD_oidD_i_eid ON Sales.Orders;

Обобщение и какво следва

И така, какво научихме от това?

iTVF е многократно използваем параметризиран израз на именувана таблица.

SQL Server използва стратегия за оптимизиране за вграждане на параметри с iTVF по подразбиране и стратегия за оптимизиране на параметризирани заявки със заявки за съхранени процедури. Добавянето на OPTION(RECOMPILE) към заявка за съхранена процедура може да доведе до оптимизиране на вграждането на параметри.

Ако искате да получите по-малко компилации и ефективно кеширане на планове и поведение при повторно използване, плановете за заявка с параметризирани процедури са правилният начин.

Плановете за iTVF заявки се кешират и могат да се използват повторно, стига да се повтарят едни и същи стойности на параметрите.

Можете удобно да комбинирате използването на оператора APPLY и iTVF, за да приложите iTVF към всеки ред от лявата таблица, като предавате колони от лявата таблица като входни данни към iTVF.

Както споменахме, има много неща за оптимизирането на iTVF. Този месец сравних iTVF и съхранените процедури по отношение на стратегията за оптимизация по подразбиране и поведението при кеширане и повторна употреба на планове. Следващия месец ще се поразровя по-задълбочено в опростяванията, произтичащи от оптимизацията за вграждане на параметри.


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. Как да намерите дублиращи се стойности в SQL таблица

  2. Свързване с Lotus Notes от Java

  3. Как да конкатенираме низове в SQL

  4. Модел на база данни за онлайн проучване. част 4

  5. Разрушете стените! Как да деблокирате данните си