Příliš často vidíme špatně napsané složité SQL dotazy běžící proti databázovým tabulkám. Spuštění takových dotazů může trvat velmi krátce nebo velmi dlouho, ale spotřebovávají obrovské množství CPU a dalších zdrojů. Přesto v mnoha případech poskytují složité dotazy aplikaci/osobě cenné informace. Proto přináší užitečné prostředky ve všech variantách aplikací.
Složitost dotazů
Podívejme se blíže na problematické dotazy. Mnohé z nich jsou složité. To může být způsobeno několika důvody:
- datový typ zvolený pro data;
- organizace a ukládání dat v databázi;
- Transformace a spojení dat v dotazu za účelem získání požadované sady výsledků.
Tyto tři klíčové faktory musíte správně zvážit a správně je implementovat, aby dotazy fungovaly optimálně.
Může se však stát téměř nemožným úkolem jak pro vývojáře databází, tak pro správce databází. Například může být výjimečně obtížné přidat nové funkce do stávajících starších systémů. Zvláště komplikovaný případ je, když potřebujete extrahovat a transformovat data ze staršího systému, abyste je mohli porovnat s daty vytvořenými novým systémem nebo funkcí. Musíte toho dosáhnout, aniž byste ovlivnili funkčnost starší aplikace.
Takové dotazy mohou zahrnovat složitá spojení, jako jsou následující:
- Kombinace podřetězce a/nebo zřetězení několika datových sloupců;
- vestavěné skalární funkce;
- Přizpůsobené UDF;
- Jakákoli kombinace porovnání klauzule WHERE a podmínek vyhledávání.
Dotazy, jak bylo popsáno dříve, mají obvykle složité přístupové cesty. A co je horší, mohou mít mnoho prohledávání tabulek a/nebo úplných prohledávání indexů s takovými kombinacemi spojení JOIN nebo hledáním.
Transformace dat a manipulace v dotazech
Musíme zdůraznit, že všechna data uložená trvale v databázové tabulce potřebují transformaci a/nebo manipulaci v určitém okamžiku, když se dotazujeme na tato data z tabulky. Transformace se může pohybovat od jednoduché transformace až po velmi složitou. V závislosti na tom, jak složitá může být, může transformace spotřebovat hodně CPU a zdrojů.
Ve většině případů proběhnou transformace provedené v JOINech poté, co jsou data přečtena a přenesena do tempdb databáze (SQL Server) nebo pracovní soubor databáze / dočasné tabulkové prostory jako v jiných databázových systémech.
Protože data v pracovním souboru nejsou indexovatelná , čas potřebný k provedení kombinovaných transformací a JOINů se exponenciálně zvyšuje. Získaná data se zvětší. Výsledné dotazy se tak díky dalšímu nárůstu dat rozvinou do úzkého hrdla výkonu.
Jak tedy může vývojář databází nebo správce databází rychle vyřešit tato omezení výkonu a také si poskytnout více času na přepracování a přepsání dotazů pro optimální výkon?
Existují dva způsoby, jak efektivně řešit takové přetrvávající problémy. Jedním z nich je použití virtuálních sloupců a/nebo funkčních indexů.
Funkční indexy a dotazy
Normálně vytváříte indexy na sloupcích, které buď označují jedinečnou sadu sloupců/hodnot v řádku (jedinečné indexy nebo primární klíče), nebo představují sadu sloupců/hodnot, které jsou nebo mohou být použity v podmínkách vyhledávání klauzule WHERE dotazu.
Pokud takové indexy nemáte a vytvořili jste složité dotazy, jak bylo popsáno výše, všimnete si následujícího:
- Snížení úrovně výkonu při použití vysvětlení dotaz a zobrazení prohledávání tabulek nebo úplných prohledávání indexů
- Velmi vysoké využití CPU a zdrojů způsobené dotazy;
- Dlouhé doby provádění.
Současné databáze tyto problémy běžně řeší tak, že umožňují vytvořit funkční nebo funkční index, jak je pojmenován v SQLServer, Oracle a MySQL (v 8.x). Nebo může být Index zapnutý založené na výrazu/výrazu indexy, stejně jako v jiných databázích (PostgreSQL a Db2).
Předpokládejme, že máte sloupec Datum_nákupu datového typu TIMESTAMP nebo DATETIME ve vaší Objednávce tabulky a tento sloupec byl indexován. Začneme se dotazovat na Objednávku tabulka s klauzulí WHERE:
SELECT ...
FROM Order
WHERE DATE(Purchase_Date) = '03.12.2020'
Tato transakce způsobí skenování celého indexu. Pokud však sloupec nebyl indexován, získáte prohledání tabulky.
Po skenování celého indexu se tento index přesune do tempdb / workfile (celá tabulka pokud získáte skenování tabulky ) před spárováním hodnoty 03.12.2020 .
Protože velká tabulka objednávek využívá spoustu CPU a zdrojů, měli byste vytvořit funkční index s výrazem DATE (Purchase_Date ) jako jeden ze sloupců indexu a zobrazený níže:
CREATE ix_DatePurchased on sales.Order(Date(Purchase_Date) desc, ... )
Přitom vytvoříte odpovídající predikát DATE (Date_Purchase_Date) =‘03.12.2020’ indexovatelné. Místo přesouvání indexu nebo tabulky do tempdb / pracovního souboru před porovnáním hodnoty tedy index zpřístupníme a/nebo naskenujeme pouze částečně. Výsledkem je nižší využití CPU a zdrojů.
Podívejte se na další příklad. Existuje zákazník tabulka se sloupci jméno, příjmení . Tyto sloupce jsou indexovány takto:
CREATE INDEX ix_custname on Customer(first_name asc, last_name asc),
Kromě toho máte zobrazení, které tyto sloupce zřetězí do customer_name sloupec:
CREATE view v_CustomerInfo( customer_name, .... ) as
select first_name ||' '|| last_name as customer_name,.....
from Customer
where ...
Máte dotaz ze systému elektronického obchodu, který hledá celé jméno zákazníka:
select c.*
from v_CustomerInfo c
where c.customer_name = 'John Smith'
....
Tento dotaz opět vytvoří úplné prohledání indexu. V nejhorším případě se bude jednat o úplné prohledání tabulky přesunutí všech dat z indexu nebo tabulky do pracovního souboru před zřetězením first_name a příjmení sloupců a odpovídající hodnotě ‚John Smith‘.
Dalším případem je vytvoření funkčního indexu, jak je znázorněno níže:
CREATE ix_fullcustname on sales.Customer( first_name ||' '|| last_name desc, ... )
Tímto způsobem můžete zřetězení v dotazu pohledu vytvořit indexovatelný predikát. Namísto úplného skenování indexu nebo skenování tabulky máte částečné skenování indexu. Takové provedení dotazu má za následek nižší využití procesoru a zdrojů, čímž se vyloučí práce v pracovním souboru a tím se zajistí rychlejší doba provádění.
Virtuální (generované) sloupce a dotazy
Generované sloupce (virtuální sloupce nebo počítané sloupce) jsou sloupce, které obsahují data generovaná za běhu. Data nelze explicitně nastavit na konkrétní hodnotu. Odkazuje na data v jiných sloupcích dotazovaných, vložených nebo aktualizovaných v dotazu DML.
Generování hodnot těchto sloupců je automatizované na základě výrazu. Tyto výrazy mohou generovat:
- Posloupnost celočíselných hodnot;
- Hodnota založená na hodnotách ostatních sloupců v tabulce;
- Může generovat hodnoty voláním vestavěných funkcí nebo uživatelem definovaných funkcí (UDF).
Stejně důležité je poznamenat, že v některých databázích (SQLServer, Oracle, PostgreSQL, MySQL a MariaDB) lze tyto sloupce nakonfigurovat tak, aby buď trvale ukládaly data s prováděním příkazů INSERT a UPDATE, nebo spouštěly základní sloupcový výraz za běhu. pokud se dotazujeme na tabulku a sloupec šetřící úložný prostor.
Pokud je však výraz komplikovaný, jako je tomu u složité logiky ve funkci UDF, úspora času provádění, zdrojů a nákladů na dotazování CPU nemusí být taková, jak se očekávalo.
Můžeme tedy nakonfigurovat sloupec tak, aby trvale ukládal výsledek výrazu v příkazu INSERT nebo UPDATE. Poté na tomto sloupci vytvoříme běžný index. Tímto způsobem ušetříme CPU, využití zdrojů a dobu provádění dotazu. Opět se může jednat o mírné zvýšení výkonu INSERT a UPDATE v závislosti na složitosti výrazu.
Podívejme se na příklad. Tabulku deklarujeme a vytvoříme index následovně:
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, ... )
Tímto způsobem přesuneme logiku zřetězení z pohledu v předchozím příkladu dolů do tabulky a trvale uložíme data. Data získáváme pomocí odpovídajícího skenování na běžném indexu. Zde je to nejlepší možný výsledek.
Přidáním vygenerovaného sloupce do tabulky a vytvořením pravidelného indexu na tomto sloupci můžeme posunout transformační logiku dolů na úroveň tabulky. Zde trvale ukládáme transformovaná data ve vkládacích nebo aktualizačních příkazech, které by jinak byly transformovány v dotazech. Skenování JOIN a INDEX bude mnohem jednodušší a rychlejší.
Funkční indexy, generované sloupce a JSON
Globální webové a mobilní aplikace používají lehké datové struktury, jako je JSON, k přesunu dat z webu/mobilního zařízení do databáze a naopak. Malé rozměry datových struktur JSON umožňují rychlý a snadný přenos dat po síti. Je snadné komprimovat JSON na velmi malou velikost ve srovnání s jinými strukturami, tedy XML. Může překonat struktury při analýze za běhu.
Kvůli zvýšenému používání datových struktur JSON mají relační databáze formát úložiště JSON buď jako datový typ BLOB, nebo datový typ CLOB. Oba tyto typy dělají data v takových sloupcích neindexovatelná tak, jak jsou.
Z tohoto důvodu zavedli dodavatelé databází funkce JSON pro dotazování a úpravu objektů JSON, protože tyto funkce můžete snadno integrovat do dotazu SQL nebo jiných příkazů DML. Tyto dotazy však závisí na složitosti objektů JSON. Jsou velmi náročné na CPU a zdroje, protože objekty BLOB a CLOB je třeba přesunout do paměti, nebo v horším případě do pracovního souboru před dotazováním a/nebo manipulací.
Předpokládejme, že máme zákazníka tabulka s detailem zákazníka data uložená jako objekt JSON ve sloupci s názvem CustomerDetail . Dotazování na tabulku jsme nastavili následovně:
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')
V tomto příkladu se dotazujeme na data pro zákazníky žijící v některých částech regionu hlavního města na Islandu. Vše aktivní data by měla být načtena do pracovního souboru před použitím vyhledávacího predikátu. Načítání však povede k příliš velkému využití CPU a zdrojů.
V souladu s tím existuje účinný postup, jak zrychlit dotazy JSON. Zahrnuje využití funkcí prostřednictvím generovaných sloupců, jak bylo popsáno dříve.
Zvýšení výkonu dosáhneme přidáním generovaných sloupců. Vygenerovaný sloupec by vyhledával v dokumentu JSON konkrétní data reprezentovaná ve sloupci pomocí funkcí JSON a uložil hodnotu do sloupce.
Tyto vygenerované sloupce můžeme indexovat a dotazovat se na ně pomocí běžného SQL, kde klauzuli vyhledávací podmínky. Proto se vyhledávání konkrétních dat v objektech JSON stává velmi rychlým.
Přidáme dva vygenerované sloupce – Country a PSČ :
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);
Také vytvoříme složený index na konkrétních sloupcích. Nyní můžeme změnit dotaz na příklad zobrazený níže:
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')
To omezuje získávání dat na aktivní zákazníky pouze v některé části regionu hlavního města Islandu. Tento způsob je rychlejší a efektivnější než předchozí dotaz.
Závěr
Celkově vzato, použitím virtuálních sloupců nebo funkčních indexů na tabulky, které způsobují potíže (CPU a dotazy náročné na zdroje), můžeme problémy poměrně rychle odstranit.
Virtuální sloupce a funkční indexy mohou pomoci s dotazováním na složité objekty JSON uložené v běžných relačních tabulkách. Musíme však předem pečlivě posoudit problémy a podle toho provést potřebné změny.
V některých případech, pokud jsou datové struktury dotazu a/nebo JSON velmi složité, může se část využití CPU a prostředků přesunout z dotazů na procesy INSERT / UPDATE. Poskytuje nám menší celkové úspory CPU a zdrojů, než se očekávalo. Pokud se setkáte s podobnými problémy, může být nevyhnutelné důkladnější přepracování tabulek a dotazů.