SQLite je oblíbená relační databáze, kterou vkládáte do své aplikace. S rostoucím množstvím dat ve vaší databázi musíte použít ladění výkonu SQLite. Tento článek pojednává o indexech a jejich úskalích, použití plánovače dotazů, režimu žurnálu Write-Ahead-Logging (WAL) a zvětšování velikosti mezipaměti. Rozvádí také důležitost měření dopadu vašich úprav pomocí automatických testů.
Úvod
SQLite je populární systém relačních databází (DB) . Na rozdíl od svých větších bratříčků založených na klient-server, jako je MySQL, SQLite lze vložit do vaší aplikace jako knihovnu . SQLite má velmi podobnou sadu funkcí a také zvládne miliony řádků, protože znáte pár tipů a triků o ladění výkonu. Jak ukážou následující části, o ladění výkonu SQLite se dá vědět víc než jen vytváření indexů.
Vytvářejte indexy, ale opatrně
Základní myšlenkou indexu je urychlit čtení konkrétních údajů , tedy SELECT
příkazy s WHERE
doložka. Indexy také urychlují třídění dat (ORDER BY
), nebo JOIN
ingové tabulky. Indexy jsou bohužel dvousečná zbraň, protože zabírají další místo na disku a zpomalují manipulaci s daty (INSERT
, UPDATE
, DELETE
).
Obecná rada je vytvářet co nejméně indexů, ale tolik, kolik je třeba . Indexy mají také smysl pouze pro větší databáze s tisíci nebo miliony řádků.
K analýze dotazů použijte plánovač dotazů
Způsob, jakým jsou indexy interně používány SQLite, je zdokumentován, ale není příliš snadné porozumět. Jak je dále rozvedeno v tomto článku, je dobré analyzovat dotaz tak, že před něj přidáte EXPLAIN QUERY PLAN
. Podívejte se na každý výstupní řádek, který má tři základní varianty:
SEARCH table ...
řádky jsou dobrým znamením – SQLite používá jeden z vašich indexů!SCAN table ... USING INDEX
je špatné znamení,SCAN table ...
je ještě horší!
Zkuste se vyhnout SCAN table [using index]
záznamy ve výstupu EXPLAIN QUERY PLAN
kdykoli je to možné, protože u větších databází narazíte na problémy s výkonem. Použijte EXPLAIN QUERY PLAN
iterativně přidávejte nebo upravujte své indexy, dokud již nebude SCAN table
zobrazí se položky.
Optimalizovat dotazy, které zahrnují IS NOT
Kontrola IS NOT ...
je drahý protože SQLite bude muset skenovat všechny řádky tabulky, i když má dotčený sloupec index . Indexy jsou užitečné pouze v případě, že hledáte konkrétní hodnoty, tj. srovnání zahrnující < (menší),> (větší) nebo = (rovný), ale neplatí pro !=(nerovný).
Úhledný malý trik je, že můžete nahradit WHERE column != value
s WHERE column > value OR column < value
. To použije index sloupce a účinně ovlivní všechny řádky, jejichž hodnota se nerovná value
. Podobně WHERE stringColumn != ''
lze nahradit výrazem WHERE stringColumn > ''
, protože řetězce jsou seřaditelné. Při použití tohoto triku se však ujistěte, že víte, jak SQLite zpracovává NULL
srovnání. Například SQLite vyhodnocuje NULL > ''
jako FALSE
.
Pokud takový srovnávací trik použijete, existuje další upozornění pro případ, že váš dotaz obsahuje WHERE
a ORDER BY
, každý s jiným sloupcem:díky tomu bude dotaz opět neefektivní. Pokud je to možné, použijte stejné ve sloupci WHERE
a ORDER BY
nebo vytvořte krycí index to zahrnuje jak WHERE
a ORDER BY
sloupec.
Zlepšete rychlost zápisu pomocí zápisu Write-Ahead-Log
Protokol Write-Ahead-Logging (WAL) režim deníku výrazně zlepšuje výkon zápisu/aktualizace ve srovnání s výchozím vrácením zpět režim deníku. Nicméně, jak je zde zdokumentováno, existuje několik upozornění . Například režim WAL není v některých operačních systémech dostupný. Existují také snížené záruky konzistence dat v případě selhání hardwaru . Nezapomeňte si přečíst několik prvních stránek, abyste pochopili, co děláte.
Zjistil jsem, že příkaz PRAGMA synchronous = NORMAL
poskytuje 3-4x zrychlení. Nastavení journal_mode
na WAL
poté opět výrazně zlepší výkon (přibližně 10x nebo více, v závislosti na operačním systému).
Kromě upozornění, která jsem již zmínil, byste si měli být vědomi také následujícího:
- Při použití režimu žurnálu WAL budou vedle databázového souboru na vašem souborovém systému dva další soubory, které mají stejný název jako databáze, ale s příponou „-shm“ a „-wal“. Normálně se o to nemusíte starat, ale pokud byste měli posílat databázi na jiný počítač, zatímco vaše aplikace běží, nezapomeňte tyto dva soubory zahrnout. SQLite zkomprimuje tyto dva soubory do hlavního souboru, kdykoli obvykle zavřete všechna otevřená databázová připojení.
- Výkon vkládání nebo aktualizace se občas sníží, kdykoli dotaz spustí sloučení obsahu souboru protokolu WAL do souboru hlavní databáze. Toto se nazývá kontrolní bod , viz zde.
- Našel jsem, že
PRAGMA
s, které měníjournal_mode
asynchronous
nezdá se, že by byly trvale uloženy v databázi. Takže vždy spouštět je znovu, kdykoli otevřu nové připojení k databázi, spíše než je spouštět při prvním vytváření tabulek.
Měřte vše
Kdykoli přidáte vylepšení výkonu, nezapomeňte změřit dopad. Automatizované (jednotkové) testy jsou k tomu skvělým přístupem. Pomáhají dokumentovat vaše zjištění pro váš tým a časem odhalí odchylné chování , např. při aktualizaci na novější verzi SQLite. Příklady toho, co můžete měřit:
- Jaký je účinek použití WAL režim deníku přes vrácení zpět režim? Jaký je účinek dalšího (prý) výkonu zvyšujícího
PRAGMA
s? - Jakmile přidáte/změníte/odeberete index, o kolik rychleji
SELECT
prohlášení se stanou? O kolik pomaleji jeINSERT/DELETE/UPDATE
prohlášení se stanou? - Kolik dalšího místa na disku spotřebují indexy?
U kteréhokoli z těchto testů zvažte jejich opakování s různými velikostmi databáze. Např. spusťte je na prázdné databázi a také na databázi, která již obsahuje tisíce (nebo miliony) záznamů. Měli byste také spustit testy na různých zařízeních a operačních systémech, zvláště pokud se vaše vývojové a produkční prostředí podstatně liší.
Vylaďte velikost mezipaměti
SQLite ukládá dočasné informace do mezipaměti (v paměti RAM), např. při vytváření výsledků SELECT
dotazu nebo při manipulaci s daty, která ještě nebyla potvrzena. Ve výchozím nastavení je tato velikost ubohých 2 MB . Moderní stolní stroje dokážou ušetřit mnohem více. Spusťte PRAGMA cache_size = -kibibytes
pro zvýšení této hodnoty (pozor na mínus podepsat před hodnotou!). Další informace naleznete zde. Opět měřte jaký dopad má toto nastavení na výkon!
Použijte REPLACE INTO k vytvoření nebo aktualizaci řádku
Nemusí to být ani tak vylepšení výkonu, jako spíše úhledný malý trik. Předpokládejme, že potřebujete aktualizovat řádek v tabulce t
nebo vytvořit řádek, pokud ještě neexistuje. Namísto použití dvou dotazů (SELECT
následovaný INSERT
nebo UPDATE
), použijte REPLACE INTO
(oficiální dokumenty).
Aby to fungovalo, přidejte další fiktivní sloupec (např. replacer
) do tabulky t
, který má UNIQUE
omezovat. Deklarace sloupce by mohla např. být ... replacer INTEGER UNIQUE ...
který je součástí vaší CREATE TABLE
prohlášení. Poté použijte dotaz jako
REPLACE INTO t (col1, col2, ..., replacer) VALUES (?,?,...,1)
Code language: SQL (Structured Query Language) (sql)
Když se tento dotaz spustí poprvé, jednoduše provede INSERT
. Když je spuštěn podruhé, UNIQUE
omezení replacer
se spustí a chování při řešení konfliktů způsobí, že starý řádek bude zrušen a automaticky se vytvoří nový. Užitečný může být také související příkaz UPSERT.
Závěr
Jakmile počet řádků ve vaší databázi naroste, stávají se vylepšení výkonu nutností. Indexy jsou nejběžnějším řešením. Vyměňují vylepšenou časovou složitost za sníženou prostorovou složitost, zlepšují rychlost čtení a zároveň negativně ovlivňují výkon modifikace dat. Ukázal jsem, že při porovnávání nerovnosti musíte být obzvláště opatrní v SELECT
příkazy, protože SQLite nemůže používat indexy pro takové druhy srovnání. Obecně doporučuji používat plánovač dotazů to vysvětluje, co se stane interně pro každý dotaz SQL. Kdykoli něco upravíte, změřte dopad!