sql >> Database >  >> RDS >> Database

Virtuele kolommen en functionele indexen

Veel te vaak zien we slecht geschreven complexe SQL-query's draaien tegen de databasetabellen. Het kan heel kort of heel lang duren voordat dergelijke zoekopdrachten worden uitgevoerd, maar ze verbruiken een enorme hoeveelheid CPU en andere bronnen. Desalniettemin leveren complexe zoekopdrachten in veel gevallen waardevolle informatie op voor de applicatie/persoon. Daarom brengt het nuttige troeven in alle soorten toepassingen.

Complexiteit van zoekopdrachten

Laten we problematische zoekopdrachten nader bekijken. Velen van hen zijn complex. Dat kan verschillende redenen hebben:

  1. Het gekozen datatype voor de data;
  2. De organisatie en opslag van de gegevens in de database;
  3. Transformatie en samenvoeging van de gegevens in een query om de gewenste resultatenset op te halen.

U moet deze drie sleutelfactoren goed bedenken en correct implementeren om query's optimaal te laten presteren.

Het kan echter een bijna onmogelijke taak worden voor zowel databaseontwikkelaars als DBA's. Het kan bijvoorbeeld bijzonder moeilijk zijn om nieuwe functionaliteit toe te voegen aan bestaande Legacy-systemen. Een bijzonder gecompliceerd geval is wanneer u de gegevens uit een verouderd systeem moet extraheren en transformeren, zodat u deze kunt vergelijken met de gegevens die door het nieuwe systeem of de nieuwe functionaliteit worden geproduceerd. U moet dit bereiken zonder de functionaliteit van de legacy-applicatie te beïnvloeden.

Dergelijke zoekopdrachten kunnen complexe joins omvatten, zoals de volgende:

  1. Een combinatie van subtekenreeks en/of aaneenschakeling van verschillende gegevenskolommen;
  2. Ingebouwde scalaire functies;
  3. Aangepaste UDF's;
  4. Elke combinatie van WHERE-componentvergelijkingen en zoekvoorwaarden.

Query's, zoals eerder beschreven, hebben meestal complexe toegangspaden. Erger nog, ze kunnen veel tabelscans en/of volledige indexscans hebben waarbij dergelijke combinaties van JOIN's of zoekopdrachten plaatsvinden.

Gegevenstransformatie en manipulaties in zoekopdrachten

We moeten erop wijzen dat alle gegevens die permanent in een databasetabel zijn opgeslagen, op een bepaald moment moeten worden getransformeerd en/of gemanipuleerd wanneer we die gegevens uit de tabel opvragen. De transformatie kan variëren van een eenvoudige transformatie tot een zeer complexe. Afhankelijk van hoe complex het kan zijn, kan de transformatie veel CPU en middelen verbruiken.

In de meeste gevallen vinden transformaties in JOIN's plaats nadat de gegevens zijn gelezen en overgebracht naar de tempdb database (SQL Server) of werkbestand database / temp-tablespaces zoals in andere databasesystemen.

Aangezien de gegevens in het werkbestand niet indexeerbaar zijn , neemt de tijd die nodig is om gecombineerde transformaties en JOIN's uit te voeren exponentieel toe. De opgehaalde gegevens worden groter. De resulterende zoekopdrachten ontwikkelen zich dus tot een prestatieknelpunt door extra gegevensgroei.

Dus, hoe kan een databaseontwikkelaar of een DBA deze prestatieknelpunten snel oplossen en zichzelf ook meer tijd geven om de query's opnieuw te ontwerpen en te herschrijven voor optimale prestaties?

Er zijn twee manieren om dergelijke hardnekkige problemen effectief op te lossen. Een daarvan is het gebruik van virtuele kolommen en/of functionele indexen.

Functionele indexen en zoekopdrachten

Normaal gesproken maakt u indexen op kolommen die ofwel een unieke set kolommen/waarden in een rij aangeven (unieke indexen of primaire sleutels) of een set kolommen/waarden vertegenwoordigen die worden of kunnen worden gebruikt in de zoekvoorwaarden van een WHERE-component van een query.

Als u dergelijke indexen niet heeft en u heeft complexe zoekopdrachten ontwikkeld zoals eerder beschreven, dan zult u het volgende opmerken:

  1. Verlaging van prestatieniveaus bij gebruik van de explain opvragen en tabelscans of volledige indexscans zien
  2. Zeer hoog CPU- en brongebruik veroorzaakt door de zoekopdrachten;
  3. Lange uitvoeringstijden.

Hedendaagse databases lossen deze problemen normaal gesproken op doordat u een functionele . kunt maken of functiegebaseerd index, zoals genoemd in SQLServer, Oracle en MySQL (v 8.x). Of het kan Index op . zijn expressie/expressie-gebaseerd indexen, zoals in andere databases (PostgreSQL en Db2).

Stel dat u een Purchase_Date-kolom . heeft van het gegevenstype TIMESTAMP of DATETIME in uw Bestelling tabel, en die kolom is geïndexeerd. We beginnen de Bestelling . te doorzoeken tabel met een WHERE-clausule:

SELECT ...
FROM Order
WHERE DATE(Purchase_Date) = '03.12.2020'

Deze transactie zorgt ervoor dat de hele index wordt gescand. Als de kolom echter niet is geïndexeerd, krijgt u een tabelscan.

Na het scannen van de hele index, wordt die index verplaatst naar tempdb / workfile (hele tafel als u een tabelscan krijgt ) voordat deze overeenkomt met de waarde 03.12.2020 .

Aangezien een grote Order-tabel veel CPU en bronnen gebruikt, moet u een functionele index maken met de DATE-expressie (Purchase_Date ) als een van de indexkolommen en hieronder weergegeven:

CREATE ix_DatePurchased on sales.Order(Date(Purchase_Date) desc, ... )

Daarbij maakt u het overeenkomende predikaat DATE (Purchase_Date) ='03.12.2020' indexeerbaar. Dus in plaats van de index of tabel naar het tempdb / werkbestand te verplaatsen voordat de waarde overeenkomt, maken we de index slechts gedeeltelijk toegankelijk en/of gescand. Het resulteert in een lager CPU- en resourcegebruik.

Kijk eens naar een ander voorbeeld. Er is een Klant tabel met de kolommen first_name, last_name . Die kolommen zijn als zodanig geïndexeerd:

CREATE INDEX ix_custname on Customer(first_name asc, last_name asc),

Bovendien heb je een weergave die deze kolommen samenvoegt in de klantnaam kolom:

CREATE view v_CustomerInfo( customer_name, .... ) as
select first_name ||' '|| last_name as customer_name,.....
from Customer
where ...

U heeft een vraag van een eCommerce-systeem dat zoekt naar de volledige klantnaam:

select c.*
from v_CustomerInfo c
where c.customer_name = 'John Smith'
....

Nogmaals, deze query zal een volledige indexscan opleveren. In het ergste geval is het een volledige tabelscan waarbij alle gegevens van de index of tabel naar het werkbestand worden verplaatst vóór de aaneenschakeling van de first_name en achternaam kolommen en overeenkomend met de waarde 'John Smith'.

Een ander geval is het creëren van een functionele index zoals hieronder getoond:

CREATE ix_fullcustname on sales.Customer( first_name ||' '|| last_name desc, ... )

Op deze manier kunt u van de aaneenschakeling in de view-query een indexeerbaar predikaat maken. In plaats van een volledige indexscan of tabelscan heeft u een gedeeltelijke indexscan. Een dergelijke uitvoering van een query resulteert in een lager CPU- en resourcegebruik, waardoor het werk in het werkbestand wordt uitgesloten en dus een snellere uitvoeringstijd wordt gegarandeerd.

Virtuele (gegenereerde) kolommen en zoekopdrachten

Gegenereerde kolommen (virtuele kolommen of berekende kolommen) zijn kolommen die de gegevens bevatten die direct worden gegenereerd. De gegevens kunnen niet expliciet worden ingesteld op een specifieke waarde. Het verwijst naar de gegevens in andere kolommen die zijn opgevraagd, ingevoegd of bijgewerkt in een DML-query.

Het genereren van waarden van dergelijke kolommen is geautomatiseerd op basis van een uitdrukking. Deze uitdrukkingen kunnen genereren:

  1. Een reeks gehele waarden;
  2. De waarde gebaseerd op de waarden van andere kolommen in de tabel;
  3. Het kan waarden genereren door ingebouwde functies of door de gebruiker gedefinieerde functies (UDF's) aan te roepen.

Het is even belangrijk op te merken dat in sommige databases (SQLServer, Oracle, PostgreSQL, MySQL en MariaDB) deze kolommen kunnen worden geconfigureerd om ofwel de gegevens permanent op te slaan met de uitvoering van de INSERT- en UPDATE-statements, of de onderliggende kolomexpressie on-the-fly uit te voeren als we de tabel en de kolom opvragen om opslagruimte te besparen.

Wanneer de expressie echter gecompliceerd is, zoals bij complexe logica in de UDF-functie, zijn de besparingen op uitvoeringstijd, resources en CPU-querykosten mogelijk niet zo groot als verwacht.

We kunnen de kolom dus zo configureren dat deze de uitkomst van de expressie permanent opslaat in een INSERT- of UPDATE-instructie. Vervolgens maken we een reguliere index voor die kolom. Op deze manier besparen we de CPU, het resourcegebruik en de uitvoeringstijd van de query. Nogmaals, het kan een lichte toename zijn in de INSERT- en UPDATE-prestaties, afhankelijk van de complexiteit van de uitdrukking.

Laten we een voorbeeld bekijken. We declareren de tabel en maken als volgt een index:

CREATE TABLE Customer as (
  customerID Int GENERATED ALWAYS AS IDENTITY,
  first_name VARCHAR(50) NOT NULL,
  last_name VARCHAR(50) NOT NULL,
  customer_name as (first_name ||' '|| last_name) PERSISTED
  ...
  );
CREATE ix_fullcustname on sales.Customer( customer_name desc, ... )

Op deze manier verplaatsen we de aaneenschakelingslogica van de weergave in het vorige voorbeeld naar beneden in de tabel en slaan de gegevens permanent op. We halen de gegevens op met behulp van een matchingscan op een reguliere index. Het is hier het best mogelijke resultaat.

Door een gegenereerde kolom aan een tabel toe te voegen en een reguliere index op die kolom te maken, kunnen we de transformatielogica naar het tabelniveau verplaatsen. Hier slaan we de getransformeerde gegevens voortdurend op in insert- of update-instructies die anders in query's zouden worden omgezet. De JOIN- en de INDEX-scans worden veel eenvoudiger en sneller.

Functionele indexen, gegenereerde kolommen en JSON

Wereldwijde web- en mobiele applicaties gebruiken lichte datastructuren zoals JSON om de data van het web/mobiele apparaat naar de database te verplaatsen en vice versa. De kleine footprint van JSON-gegevensstructuren maakt de gegevensoverdracht via het netwerk snel en eenvoudig. Het is gemakkelijk om JSON tot een zeer klein formaat te comprimeren in vergelijking met andere structuren, d.w.z. XML. Het kan beter presteren dan structuren bij runtime-parsing.

Vanwege het toegenomen gebruik van JSON-gegevensstructuren, hebben relationele databases het JSON-opslagformaat als BLOB-gegevenstype of CLOB-gegevenstype. Beide typen zorgen ervoor dat de gegevens in dergelijke kolommen niet-indexeerbaar zijn.

Om deze reden hebben de databaseleveranciers JSON-functies geïntroduceerd voor het opvragen en wijzigen van JSON-objecten, aangezien u deze functies eenvoudig kunt integreren in de SQL-query of andere DML-opdrachten. Deze query's zijn echter afhankelijk van de complexiteit van JSON-objecten. Ze zijn erg CPU- en resource-intensief, omdat BLOB- en CLOB-objecten in het geheugen moeten worden geladen, of, erger nog, in het werkbestand voor het opvragen en/of manipuleren.

Stel dat we een Klant . hebben tabel met het Klantdetail gegevens opgeslagen als een JSON-object in een kolom met de naam CustomerDetail . We stellen het opvragen van de tabel als volgt in:

SELECT CustomerID,
  JSON_VALUE(CustomerDetail, '$.customer.Name') AS Name,
  JSON_VALUE(CustomerDetail, '$.customer.Surname') AS Surname,
  JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
  JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
  + JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
  JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
  AND JSON_VALUE(CustomerDetail, '$.customer.address.Country') = 'Iceland'
  AND JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') IN (101,102,110,210,220)
  AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')

In dit voorbeeld vragen we de gegevens op voor klanten die in sommige delen van het Hoofdstedelijk Gewest in IJsland wonen. Allemaal Actief gegevens moeten worden opgehaald in het werkbestand voordat u het zoekpredikaat toepast. Toch zal het ophalen resulteren in een te groot CPU- en resourcegebruik.

Dienovereenkomstig is er een effectieve procedure om JSON-query's sneller te laten verlopen. Het gaat om het gebruik van de functionaliteit via gegenereerde kolommen, zoals eerder beschreven.

We bereiken de prestatieverbetering door gegenereerde kolommen toe te voegen. Een gegenereerde kolom zou door het JSON-document zoeken naar specifieke gegevens die in de kolom worden weergegeven met behulp van de JSON-functies en de waarde in de kolom opslaan.

We kunnen deze gegenereerde kolommen indexeren en opvragen met behulp van reguliere SQL waar clausule-zoekvoorwaarden. Daarom wordt het zoeken naar bepaalde gegevens in JSON-objecten erg snel.

We voegen twee gegenereerde kolommen toe - Land en Postcode :

ALTER TABLE Customer
ADD Country as JSON_VALUE(CustomerDetail,'$.customer.address.Country');
ALTER TABLE Customer
ADD PostCode as JSON_VALUE(CustomerDetail,'$.customer.address.PostCode');

CREATE INDEX ix_CountryPostCode on Country(Country asc,PostCode asc);

We maken ook een samengestelde index op de specifieke kolommen. Nu kunnen we de query wijzigen in het onderstaande voorbeeld:

SELECT CustomerID,
  JSON_VALUE(CustomerDetail, '$.customer.customer.Name') AS Name,
  JSON_VALUE(CustomerDetail, '$.customer.customer.Surname') AS Surname,
  JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
  JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
  + JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
  JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
  AND Country = 'Iceland'
  AND PostCode IN (101,102,110,210,220)
  AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')

Dit beperkt het ophalen van gegevens tot alleen actieve klanten in een deel van het IJslandse Hoofdstedelijk Gewest. Deze manier is sneller en efficiënter dan de vorige zoekopdracht.

Conclusie

Al met al kunnen we problemen vrij snel oplossen door virtuele kolommen of functionele indexen toe te passen op tabellen die problemen veroorzaken (CPU en zware zoekopdrachten).

Virtuele kolommen en functionele indexen kunnen helpen bij het opvragen van complexe JSON-objecten die zijn opgeslagen in reguliere relationele tabellen. We moeten de problemen echter vooraf zorgvuldig beoordelen en dienovereenkomstig de nodige wijzigingen aanbrengen.

In sommige gevallen, als de query- en/of JSON-gegevensstructuren erg complex zijn, kan een deel van het CPU- en resourcegebruik verschuiven van de query's naar de INSERT / UPDATE-processen. Het levert ons minder totale CPU- en resourcebesparingen op dan verwacht. Als u soortgelijke problemen ondervindt, kan het onvermijdelijk zijn dat de tabellen en query's grondiger moeten worden herzien.


  1. Een lijst met privéprocedures/-functies ophalen uit een pakkettekst

  2. Forceer in Postgresql uniek op combinatie van twee kolommen

  3. Hoe kan ik een taak plannen om dagelijks een SQL-query uit te voeren?

  4. SQL Server AlwaysOn-functies gebruiken