sql >> Database >  >> RDS >> Database

Grondbeginselen van tabeluitdrukkingen, deel 12 - Inline tabelwaardige functies

Dit artikel is het twaalfde deel in een serie over benoemde tabeluitdrukkingen. Tot dusverre heb ik afgeleide tabellen en CTE's behandeld, dit zijn benoemde tabeluitdrukkingen met een instructiebereik, en views, die herbruikbare benoemde tabeluitdrukkingen zijn. Deze maand introduceer ik inline tabelwaardige functies, of iTVF's, en beschrijf hun voordelen in vergelijking met de andere genoemde tabeluitdrukkingen. Ik vergelijk ze ook met opgeslagen procedures, waarbij ik me vooral richt op verschillen in termen van standaardoptimalisatiestrategie, en plancaching en hergebruikgedrag. Er valt veel te bespreken op het gebied van optimalisatie, dus ik zal de discussie deze maand beginnen en volgende maand voortzetten.

In mijn voorbeelden gebruik ik een voorbeelddatabase met de naam TSQLV5. U vindt het script dat het maakt en vult hier en het ER-diagram hier.

Wat is een inline tabelwaarde-functie?

Vergeleken met de eerder behandelde benoemde tabeluitdrukkingen, lijken iTVF's vooral op views. Net als weergaven worden iTVF's gemaakt als een permanent object in de database en kunnen ze daarom opnieuw worden gebruikt door gebruikers die toestemming hebben om ermee te communiceren. Het belangrijkste voordeel dat iTVF's hebben in vergelijking met weergaven is het feit dat ze invoerparameters ondersteunen. Dus de gemakkelijkste manier om een ​​iTVF te beschrijven is als een geparametriseerde weergave, hoewel je het technisch gezien maakt met een CREATE FUNCTION-instructie en niet met een CREATE VIEW-instructie.

Het is belangrijk om iTVF's niet te verwarren met multi-statement table-valued functions (MSTVF's). De eerste is een inlinable benoemde tabelexpressie op basis van een enkele query die lijkt op een weergave en is de focus van dit artikel. De laatste is een programmatische module die een tabelvariabele als uitvoer retourneert, met een stroom met meerdere instructies in de hoofdtekst waarvan het doel is om de geretourneerde tabelvariabele met gegevens te vullen.

Syntaxis

Hier is de T-SQL-syntaxis voor het maken van een iTVF:

MAAK [ OF WIJZIG ] FUNCTIE [ . ]

[ () ]

RETOURENTABEL

[ MET ]

AS

RETOUR

[; ]

Let in de syntaxis op de mogelijkheid om invoerparameters te definiëren.

Het doel van het SCHEMABIDNING-attribuut is hetzelfde als bij weergaven en moet worden geëvalueerd op basis van vergelijkbare overwegingen. Voor details, zie deel 10 in de serie.

Een voorbeeld

Stel dat u als voorbeeld voor een iTVF een herbruikbare benoemde tabelexpressie moet maken die als invoer een klant-ID (@custid) en een nummer (@n) accepteert en het gevraagde aantal van de meest recente bestellingen uit de tabel Sales.Orders retourneert voor de input klant.

U kunt deze taak niet implementeren met een weergave, omdat weergaven geen ondersteuning bieden voor invoerparameters. Zoals gezegd, kun je een iTVF zien als een geparametriseerde weergave en als zodanig is het de juiste tool voor deze taak.

Voordat u de functie zelf implementeert, volgt hier de code om een ​​ondersteunende index te maken in de tabel Sales.Orders:

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

En hier is de code om de functie te maken, genaamd 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

Net als bij basistabellen en views, specificeert u iTVF's in de FROM-clausule van een SELECT-instructie wanneer u gegevens wilt ophalen. Hier is een voorbeeld van het aanvragen van de drie meest recente bestellingen voor klant 1:

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

Ik noem dit voorbeeld Query 1. Het plan voor Query 1 wordt getoond in figuur 1.

Figuur 1:Plan voor Query 1

Wat is er inline aan iTVF's?

Als je je afvraagt ​​waar de term inline vandaan komt in inline tabelwaardige functies heeft het te maken met hoe ze worden geoptimaliseerd. Het inlining-concept is van toepassing op alle vier soorten benoemde tabeluitdrukkingen die T-SQL ondersteunt, en omvat gedeeltelijk wat ik in deel 4 van de serie beschreef als unnesting/substitution. Zorg ervoor dat je de relevante sectie in deel 4 opnieuw bezoekt als je een opfriscursus nodig hebt.

Zoals je in figuur 1 kunt zien, kon SQL Server, dankzij het feit dat de functie inline werd geplaatst, een optimaal plan maken dat rechtstreeks samenwerkt met de onderliggende indexen van de basistabel. In ons geval voert het plan een zoekactie uit in de ondersteunende index die u eerder hebt gemaakt.

iTVF's gaan nog een stap verder met het inlining-concept door standaard optimalisatie van parameterinbedding toe te passen. Paul White beschrijft de optimalisatie van parameterinbedding in zijn uitstekende artikel Parameter Sniffing, Embedding, and the RECOMPILE Options. Met optimalisatie van het insluiten van parameters worden queryparameterreferenties vervangen door de letterlijke constante waarden van de huidige uitvoering, en vervolgens wordt de code met de constanten geoptimaliseerd.

Merk in het plan in figuur 1 op dat zowel het seek-predikaat van de index Seek-operator als de top-expressie van de Top-operator de ingebedde letterlijke constante waarden 1 en 3 van de huidige uitvoering van de query weergeven. Ze tonen niet respectievelijk de parameters @custid en @n.

Bij iTVF's wordt standaard optimalisatie van het inbedden van parameters gebruikt. Bij opgeslagen procedures worden query's met parameters standaard geoptimaliseerd. U moet OPTION(RECOMPILE) toevoegen aan de query van een opgeslagen procedure om optimalisatie van het insluiten van parameters aan te vragen. Binnenkort meer details over optimalisatie van iTVF's versus opgeslagen procedures, inclusief implicaties.

Gegevens wijzigen via iTVF's

Bedenk uit deel 11 van de serie dat zolang aan bepaalde vereisten wordt voldaan, benoemde tabeluitdrukkingen een doelwit kunnen zijn van wijzigingsinstructies. Deze mogelijkheid is van toepassing op iTVF's, vergelijkbaar met de manier waarop deze van toepassing is op weergaven. Hier is bijvoorbeeld de code die u zou kunnen gebruiken om de drie meest recente bestellingen van klant 1 te verwijderen (voer dit niet echt uit):

DELETE FROM Sales.GetTopCustOrders(1, 3);

Met name in onze database zou een poging om deze code uit te voeren mislukken vanwege de handhaving van de referentiële integriteit (de betrokken bestellingen hebben toevallig gerelateerde orderregels in de tabel Sales.OrderDetails), maar het is geldige en ondersteunde code.

iTVF's versus opgeslagen procedures

Zoals eerder vermeld, is de standaardstrategie voor het optimaliseren van query's voor iTVF's anders dan die voor opgeslagen procedures. Bij iTVF's is de standaardinstelling om optimalisatie van het insluiten van parameters te gebruiken. Bij opgeslagen procedures is de standaardinstelling het optimaliseren van geparametriseerde query's tijdens het toepassen van parametersniffing. Om parameterinsluiting voor een opgeslagen procedurequery te krijgen, moet u OPTION(RECOMPILE) toevoegen.

Zoals met veel optimalisatiestrategieën en -technieken, heeft het inbedden van parameters zijn voor- en nadelen.

Het belangrijkste pluspunt is dat het queryvereenvoudigingen mogelijk maakt die soms kunnen resulteren in efficiëntere plannen. Sommige van die vereenvoudigingen zijn werkelijk fascinerend. Paul demonstreert dit met opgeslagen procedures in zijn artikel, en ik zal dit volgende maand demonstreren met iTVF's.

Het belangrijkste minpunt van optimalisatie van parameterinbedding is dat u geen efficiënt plancaching en hergebruikgedrag krijgt zoals bij geparametriseerde plannen. Met elke afzonderlijke combinatie van parameterwaarden krijgt u een afzonderlijke querytekenreeks en dus een afzonderlijke compilatie die resulteert in een afzonderlijk cacheplan. Met iTVF's met constante invoer kunt u het hergebruik van plannen verkrijgen, maar alleen als dezelfde parameterwaarden worden herhaald. Het is duidelijk dat een opgeslagen procedure-query met OPTION(RECOMPILE) een plan niet opnieuw zal gebruiken, zelfs niet wanneer dezelfde parameterwaarden op verzoek worden herhaald.

Ik zal drie gevallen demonstreren:

  1. Herbruikbare plannen met constanten die het resultaat zijn van de standaard optimalisatie van het insluiten van parameters voor iTVF-query's met constanten
  2. Herbruikbare geparametriseerde plannen die het resultaat zijn van de standaardoptimalisatie van geparametriseerde opgeslagen procedurequery's
  3. Niet-herbruikbare plannen met constanten die het resultaat zijn van optimalisatie van parameterinbedding voor opgeslagen procedurequery's met OPTION(RECOMPILE)

Laten we beginnen met geval #1.

Gebruik de volgende code om onze iTVF te doorzoeken met @custid =1 en @n =3:

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

Ter herinnering:dit zou de tweede uitvoering van dezelfde code zijn, aangezien u deze al eerder met dezelfde parameterwaarden hebt uitgevoerd, wat resulteert in het plan dat wordt weergegeven in afbeelding 1.

Gebruik de volgende code om de iTVF op te vragen met @custid =2 en @n =3 één keer:

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

Ik noem deze code Query 2. Het plan voor Query 2 wordt getoond in figuur 2.

Figuur 2:Plan voor Query 2

Bedenk dat het plan in Afbeelding 1 voor Query 1 verwees naar de constante klant-ID 1 in het zoekpredikaat, terwijl dit plan verwijst naar de constante klant-ID 2.

Gebruik de volgende code om de uitvoeringsstatistieken van query's te onderzoeken:

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

Deze code genereert de volgende uitvoer:

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)

Er zijn hier twee afzonderlijke plannen gemaakt:een voor de zoekopdracht met klant-ID 1, die twee keer is gebruikt, en een andere voor de zoekopdracht met klant-ID 2, die een keer is gebruikt. Met een zeer groot aantal verschillende combinaties van parameterwaarden, krijg je een groot aantal compilaties en gecachte plannen.

Laten we verder gaan met geval #2:de standaard optimalisatiestrategie van geparametriseerde opgeslagen procedurequery's. Gebruik de volgende code om onze vraag in te kapselen in een opgeslagen procedure genaamd 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

Gebruik de volgende code om de opgeslagen procedure uit te voeren met @custid =1 en @n =3 tweemaal:

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

De eerste uitvoering activeert de optimalisatie van de query, wat resulteert in het geparametriseerde plan dat wordt weergegeven in Afbeelding 3:

Figuur 3:Plan voor Sales.GetTopCustOrders2 proc

Let op de verwijzing naar de parameter @custid in het zoekpredikaat en naar de parameter @n in de bovenste uitdrukking.

Gebruik de volgende code om de opgeslagen procedure uit te voeren met @custid =2 en @n =3 één keer:

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

Het in de cache opgeslagen geparametriseerde plan dat wordt getoond in figuur 3 wordt opnieuw hergebruikt.

Gebruik de volgende code om de uitvoeringsstatistieken van query's te onderzoeken:

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

Deze code genereert de volgende uitvoer:

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

(1 row affected)

Er is slechts één geparametriseerd plan gemaakt en in de cache opgeslagen, en drie keer gebruikt, ondanks de veranderende klant-ID-waarden.

Laten we verder gaan met geval #3. Zoals vermeld, kunt u met opgeslagen procedure-query's de optimalisatie van het insluiten van parameters krijgen als u OPTION (RECOMPILE) gebruikt. Gebruik de volgende code om de procedurequery te wijzigen om deze optie op te nemen:

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

Voer de procedure uit met @custid =1 en @n =3 twee keer:

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

U krijgt hetzelfde plan als eerder in figuur 1 met de ingebedde constanten.

Voer de procedure uit met @custid =2 en @n =3 één keer:

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

U krijgt hetzelfde plan als eerder in figuur 2 met de ingebedde constanten.

Bestudeer de uitvoeringsstatistieken van zoekopdrachten:

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

Deze code genereert de volgende uitvoer:

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

(1 row affected)

Het aantal uitvoeringen toont 1, wat alleen de laatste uitvoering weergeeft. SQL Server slaat het laatst uitgevoerde plan op in de cache, zodat het statistieken voor die uitvoering kan tonen, maar op verzoek wordt het plan niet opnieuw gebruikt. Als u het plan aanvinkt dat wordt weergegeven onder het kenmerk query_plan, ziet u dat dit het plan is dat is gemaakt voor de constanten in de laatste uitvoering, eerder weergegeven in Afbeelding 2.

Als u op zoek bent naar minder compilaties en efficiënt plancaching en hergebruik, dan is de standaardaanpak voor optimalisatie van opgeslagen procedures van geparametriseerde query's de juiste keuze.

Er is een groot voordeel dat een op iTVF gebaseerde implementatie heeft boven een op opgeslagen procedure gebaseerde implementatie - wanneer u de functie op elke rij in een tabel moet toepassen en kolommen uit de tabel als invoer moet doorgeven. Stel bijvoorbeeld dat u de drie meest recente bestellingen voor elke klant in de tabel Verkoop.Klanten moet retourneren. Geen enkele queryconstructie stelt u in staat om een ​​opgeslagen procedure per rij in een tabel toe te passen. Je zou een iteratieve oplossing met een cursor kunnen implementeren, maar het is altijd een goede dag om cursors te vermijden. Door de APPLY-operator te combineren met een iTVF-oproep, kunt u de taak netjes en netjes uitvoeren, zoals:

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

Deze code genereert de volgende output (afgekort):

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)

De functieaanroep wordt inline geplaatst en de verwijzing naar de parameter @custid wordt vervangen door de correlatie C.custid. Dit resulteert in het in figuur 4 getoonde plan.

Figuur 4:Plan voor query met APPLY en Sales.GetTopCustOrders iTVF

Het plan scant een index op de Sales.Customers-tabel om de set klant-ID's te krijgen en past een zoekopdracht toe in de ondersteunende index die u eerder op Sales.Orders per klant hebt gemaakt. Er is maar één plan omdat de functie in de buitenste query is opgenomen en verandert in een gecorreleerde of een laterale join. Dit plan is zeer efficiënt, vooral wanneer de custid-kolom in Sales.Orders erg compact is, wat betekent dat er een klein aantal verschillende klant-ID's is.

Natuurlijk zijn er andere manieren om deze taak uit te voeren, zoals het gebruik van een CTE met de ROW_NUMBER-functie. Een dergelijke oplossing werkt meestal beter dan de op APPLY gebaseerde oplossing wanneer de custid-kolom in de tabel Sales.Orders een lage dichtheid heeft. Hoe dan ook, de specifieke taak die ik in mijn voorbeelden heb gebruikt, is niet zo belangrijk voor de doeleinden van onze discussie. Mijn punt was om de verschillende optimalisatiestrategieën uit te leggen die SQL Server gebruikt met de verschillende tools.

Als je klaar bent, gebruik je de volgende code voor het opschonen:

DROP INDEX IF EXISTS idx_nc_cid_odD_oidD_i_eid ON Sales.Orders;

Samenvatting en wat nu

Wat hebben we hiervan geleerd?

Een iTVF is een herbruikbare geparametriseerde benoemde tabeluitdrukking.

SQL Server gebruikt standaard een optimalisatiestrategie voor het insluiten van parameters met iTVF's en een strategie voor het optimaliseren van query's met parameters voor opgeslagen procedurequery's. Het toevoegen van OPTION(RECOMPILE) aan een opgeslagen procedurequery kan resulteren in optimalisatie van het insluiten van parameters.

Als u minder compilaties en efficiënt plancaching en hergebruik wilt, zijn geparametriseerde procedurequeryplannen de juiste keuze.

Plannen voor iTVF-query's worden in de cache opgeslagen en kunnen opnieuw worden gebruikt, zolang dezelfde parameterwaarden worden herhaald.

U kunt het gebruik van de APPLY-operator en een iTVF gemakkelijk combineren om de iTVF op elke rij uit de linkertabel toe te passen, waarbij kolommen uit de linkertabel worden doorgegeven als invoer voor de iTVF.

Zoals eerder vermeld, valt er veel te vertellen over de optimalisatie van iTVF's. Deze maand heb ik iTVF's en opgeslagen procedures vergeleken in termen van de standaard optimalisatiestrategie en plancaching en hergebruikgedrag. Volgende maand ga ik dieper in op vereenvoudigingen als gevolg van optimalisatie van het insluiten van parameters.


  1. PHP MYSQL UPDATE indien aanwezig of INSERT indien niet?

  2. SQL Server 2017 Stap voor stap installatie -2

  3. Oracle-vertraging tussen commit en select

  4. Haal BLOB op uit de BFILE-kolom in Oracle