sql >> Databáze >  >> RDS >> SQLite

Pasti a nástrahy SQLite

SQLite je oblíbená relační databáze, kterou vkládáte do své aplikace. Existuje však mnoho pastí a nástrah, kterým byste se měli vyhnout. Tento článek popisuje několik úskalí (a jak se jim vyhnout), jako je použití ORM, jak získat zpět místo na disku, dbát na maximální počet proměnných dotazu, datové typy sloupců a jak zacházet s velkými celými čísly.

Úvod

SQLite je populární systém relačních databází (DB) . Má velmi podobnou sadu funkcí jako jeho větší bratři, jako je MySQL , což jsou systémy na bázi klient/server. SQLite je však vložený databáze . Může být součástí vašeho programu jako statická (nebo dynamická) knihovna. Toto zjednodušuje nasazení , protože není nutný žádný samostatný serverový proces. Knihovny vazeb a obalů vám umožní přistupovat k SQLite ve většině programovacích jazyků .

S SQLite jsem intenzivně pracoval při vývoji BSync v rámci své disertační práce. Tento článek je (náhodný) seznam pastí a nástrah, na které jsem během vývoje narazil . Doufám, že pro vás budou užitečné a vyvarujete se chyb, které jsem kdysi dělal já.

Pasti a nástrahy

Používejte knihovny ORM opatrně

Knihovny Object-Relational Mapping (ORM) abstrahují detaily z konkrétních databázových strojů a jejich syntaxi (jako jsou specifické příkazy SQL) do vysoce-úrovňového objektově orientovaného API. Existuje mnoho knihoven třetích stran (viz Wikipedie). Knihovny ORM mají několik výhod:

  • Při vývoji šetří čas , protože rychle mapují váš kód/třídy do DB struktur,
  • Jsou často multiplatformní , tj. umožnit náhradu konkrétní technologie DB (např. SQLite s MySQL),
  • Nabízejí pomocný kód pro migraci schématu .

Mají však také několik vážných nevýhod měli byste si být vědomi:

  • Umožňují práci s databázemi objevit snadné . Nicméně ve skutečnosti mají DB motory složité detaily, které prostě musíte znát . Jakmile se něco pokazí, např. když knihovna ORM vyvolá výjimky, kterým nerozumíte, nebo když se výkon za běhu sníží,čas vývoje, který jste ušetřili používáním ORM, bude rychle spotřebován úsilím potřebným k odladění problému . Pokud například nevíte, jaké indexy budete mít potíže s odstraňováním překážek výkonu způsobených ORM, když automaticky nevytváří všechny požadované indexy. V podstatě:neexistuje žádný oběd zdarma.
  • Vzhledem k abstrakci konkrétního dodavatele DB jsou funkce specifické pro daného dodavatele buď těžko dostupné, nebo nejsou dostupné vůbec .
  • Existuje určitá výpočetní režie ve srovnání s přímým psaním a prováděním SQL dotazů. Řekl bych však, že tento bod je v praxi sporný, protože je běžné, že po přechodu na vyšší úroveň abstrakce ztratíte výkon.

Používání knihovny ORM je nakonec věcí osobních preferencí. Pokud tak učiníte, buďte připraveni na to, že se budete muset dozvědět o zvláštnostech relačních databází (a výhradách specifických pro dodavatele), jakmile dojde k neočekávanému chování nebo omezení výkonu.

Zahrnout tabulku migrací od začátku

Pokud ne pomocí knihovny ORM se budete muset postarat o migraci schématu DB . To zahrnuje napsání migračního kódu, který změní vaše schémata tabulek a nějakým způsobem transformuje uložená data. Doporučuji vytvořit tabulku nazvanou „migrace“ nebo „verze“ s jedním řádkem a sloupcem, ve kterých je jednoduše uložena verze schématu, např. pomocí monotónně rostoucího celého čísla. To umožňuje vaší migrační funkci zjistit, které migrace je ještě třeba použít. Kdykoli byl krok migrace úspěšně dokončen, váš kód migračních nástrojů zvýší toto počítadlo prostřednictvím UPDATE SQL příkaz.

Automaticky vytvořený řádkový sloupec

Kdykoli vytvoříte tabulku, SQLite automaticky vytvoří INTEGER sloupec s názvem rowid pro vás – pokud jste nezadali WITHOUT ROWID klauzule (ale je pravděpodobné, že jste o této klauzuli nevěděli). rowid řádek je sloupec primárního klíče. Pokud také sami určíte takový sloupec primárního klíče (např. pomocí syntaxe some_column INTEGER PRIMARY KEY ) tento sloupec bude jednoduše alias pro rowid . Zde najdete další informace, které popisují totéž poněkud tajemnými slovy. Všimněte si, že SELECT * FROM table prohlášení nebude zahrnout rowid ve výchozím nastavení – musíte požádat o rowid sloupec explicitně.

Ověřte, že PRAGMA opravdu funguje

Mimo jiné PRAGMA příkazy slouží ke konfiguraci nastavení databáze nebo k vyvolání různých funkcionalití (oficiální dokumenty). Existují však nezdokumentované vedlejší účinky, kdy někdy nastavení proměnné ve skutečnosti nemá žádný účinek . Jinými slovy, nefunguje a tiše selže.

Pokud například vydáte následující výpisy v daném pořadí, poslední prohlášení nebude mít nějaký účinek. Proměnná auto_vacuum má stále hodnotu 0 (NONE ), bez dobrého důvodu.

PRAGMA journal_mode = WAL
PRAGMA synchronous = NORMAL
PRAGMA auto_vacuum = INCREMENTAL
Code language: SQL (Structured Query Language) (sql)

Hodnotu proměnné můžete přečíst spuštěním PRAGMA variableName a vynechání rovnítka a hodnoty.

Chcete-li opravit výše uvedený příklad, použijte jiné pořadí. Použití řazení řádků 3, 1, 2 bude fungovat podle očekávání.

Možná budete chtít zahrnout takové kontroly do své výroby kód, protože tyto vedlejší účinky mohou záviset na konkrétní verzi SQLite a na tom, jak byla vytvořena. Knihovna použitá v produkci se může lišit od té, kterou jste použili během vývoje.

Nárokování místa na disku pro velké databáze

Ve výchozím nastavení velikost databázového souboru SQLite monotónně roste . Smazáním řádků označíte pouze konkrétní stránky jako volné , takže je lze použít k INSERT data v budoucnu. Chcete-li skutečně získat zpět místo na disku a zrychlit výkon, existují dvě možnosti:

  1. Spusťte příkaz VACUUM prohlášení . Má to však několik vedlejších účinků:
    • Zamkne celou databázi. Během VACUUM nelze provádět žádné souběžné operace operace.
    • Trvá to dlouho (u větších databází), protože se interně obnovuje DB v samostatném dočasném souboru a nakonec odstraní původní databázi a nahradí ji tímto dočasným souborem.
    • Dočasný soubor spotřebuje další místo na disku, zatímco operace běží. Není tedy dobrý nápad spouštět VACUUM v případě, že máte málo místa na disku. Stále byste to mohli udělat, ale museli byste pravidelně kontrolovat, zda (freeDiskSpace - currentDbFileSize) > 0 .
  2. Použijte PRAGMA auto_vacuum = INCREMENTAL při vytváření DB. Udělejte to PRAGMA první výpis po vytvoření souboru! To umožňuje určité vnitřní vedení a pomáhá databázi získat zpět místo, kdykoli zavoláte PRAGMA incremental_vacuum(N) . Toto volání získá zpět až N stránky. Oficiální dokumenty poskytují další podrobnosti a také další možné hodnoty pro auto_vacuum .
    • Poznámka:Můžete určit, kolik volného místa na disku (v bajtech) by se získalo voláním PRAGMA incremental_vacuum(N) :vynásobte hodnotu vrácenou PRAGMA freelist_count s PRAGMA page_size .

Lepší možnost závisí na vašem kontextu. Pro velmi velké databázové soubory doporučuji možnost 2 , protože možnost 1 by vaše uživatele obtěžovala minutami nebo hodinami čekání na vyčištění databáze. Možnost 1 je vhodná pro menší databáze . Jeho další výhodou je výkon z DB zlepšía (což neplatí pro možnost 2), protože rekreace eliminuje vedlejší účinky fragmentace dat.

Dbejte na maximální počet proměnných v dotazech

Ve výchozím nastavení je maximální počet proměnných („parametry hostitele“), které můžete v dotazu použít, pevně zakódován na 999 (viz zde část Maximální počet parametrů hostitele v jednom příkazu SQL ). Tento limit se může lišit, protože se jedná o dobu kompilace parametr, jehož výchozí hodnotu jste vy (nebo kdokoli jiný kompiloval SQLite) mohli změnit.

To je v praxi problematické, protože není neobvyklé, že vaše aplikace poskytuje DB engine (libovolně velký) seznam. Například pokud chcete hromadně-DELETE (nebo SELECT ) řádky založené například na seznamu ID. Prohlášení jako

DELETE FROM some_table WHERE rowid IN (?, ?, ?, ?, <999 times "?, ">, ?)Code language: SQL (Structured Query Language) (sql)

vyvolá chybu a nedokončí se.

Chcete-li tento problém vyřešit, zvažte následující kroky:

  • Analyzujte své seznamy a rozdělte je na menší seznamy,
  • Pokud bylo rozdělení nutné, nezapomeňte použít BEGIN TRANSACTION a COMMIT napodobit atomicitu, kterou by měl jeden příkaz .
  • Ujistěte se, že zvažte také další ? proměnné, které můžete použít ve svém dotazu a které nesouvisejí se seznamem příchozí pošty (např. ? proměnné používané v ORDER BY podmínka), takže celkem počet proměnných nepřekračuje limit.

Alternativním řešením je použití dočasných tabulek. Cílem je vytvořit dočasnou tabulku, vložit proměnné dotazu jako řádky a pak tuto dočasnou tabulku použít v dílčím dotazu, např.

DROP TABLE IF EXISTS temp.input_data
CREATE TABLE temp.input_data (some_column TEXT UNIQUE)
# Insert input data, running the next query multiple times
INSERT INTO temp.input_data (some_column) VALUES (...)
# The above DELETE statement now changes to this one:
DELETE FROM some_table WHERE rowid IN (SELECT some_column from temp.input_data)Code language: SQL (Structured Query Language) (sql)

Pozor na afinitu typu SQLite

Sloupce SQLite nejsou přesně zapsány a konverze nemusí nutně probíhat tak, jak byste očekávali. Typy, které poskytujete, jsou pouze nápovědy . SQLite bude často ukládat data jakýchkoli zadejte jeho originál typu a převést data na typ sloupce pouze v případě, že je převod bezeztrátový. Můžete například jednoduše vložit "hello" řetězec do INTEGER sloupec. SQLite si nebude stěžovat ani vás neupozorní na neshody typu. Naopak, nemůžete očekávat, že data vrácená SELECT příkaz INTEGER sloupec je vždy INTEGER . Tyto tipy typu jsou v SQLite-speak označovány jako „typová afinita“, viz zde. Nezapomeňte si pozorně prostudovat tuto část příručky SQLite, abyste lépe porozuměli významu typů sloupců, které zadáváte při vytváření nových tabulek.

Pozor na velká celá čísla

SQLite podporuje podepsané 64bitová celá čísla , které může ukládat nebo s nimi provádět výpočty. Jinými slovy, pouze čísla od -2^63 na (2^63) - 1 jsou podporovány, protože k reprezentaci znaménka je potřeba jeden bit!

To znamená, že pokud očekáváte práci s většími čísly, např. 128bitová celá čísla (se znaménkem) nebo 64bitová celá čísla bez znaménka, musíte převést data na text před vloženímem .

Hrůza začíná, když to ignorujete a jednoduše vložíte větší čísla (jako celá čísla). SQLite si nebude stěžovat a ukládat zaoblené číslo! Pokud například vložíte 2^63 (což je již mimo podporovaný rozsah), SELECT ed hodnota bude 9223372036854776000, nikoli 2^63=9223372036854775808. V závislosti na používaném programovacím jazyce a knihovně vazeb se však chování může lišit! Například vazba sqlite3 Pythonu kontroluje takovéto přetečení celých čísel!

Nepoužívejte REPLACE() pro cesty k souborům

Představte si, že ukládáte relativní nebo absolutní cesty k souboru v TEXT sloupec v SQLite, např. pro sledování souborů ve skutečném systému souborů. Zde je příklad tří řádků:

foo/test.txt
foo/bar/
foo/bar/x.y

Předpokládejme, že chcete přejmenovat adresář „foo“ na „xyz“. Jaký SQL příkaz byste použili? Tento?

REPLACE(path_column, old_path, new_path) Code language: SQL (Structured Query Language) (sql)

To jsem dělal, dokud se nezačaly dít divné věci. Problém s REPLACE() je, že nahradí vše výskytů. Pokud existoval řádek s cestou „foo/bar/foo/“, pak REPLACE(column_name, 'foo/', 'xyz/') způsobí zmatek, protože výsledek nebude „xyz/bar/foo/“, ale „xyz/bar/xyz/“.

Lepší řešení je něco jako

UPDATE mytable SET path_column = 'xyz/' || substr(path_column, 4) WHERE path_column GLOB 'foo/*'"Code language: SQL (Structured Query Language) (sql)

4 odráží délku staré cesty (v tomto případě ‚foo/‘). Všimněte si, že jsem použil GLOB místo LIKE aktualizovat pouze ty řádky, které začínají s „foo/“.

Závěr

SQLite je fantastický databázový stroj, kde většina příkazů funguje podle očekávání. Konkrétní složitosti, jako jsou ty, které jsem právě představil, však stále vyžadují pozornost vývojáře. Kromě tohoto článku si také přečtěte oficiální dokumentaci k upozorněním SQLite.

Setkali jste se v minulosti s jinými výhradami? Pokud ano, dejte mi vědět v komentářích.


  1. hromadné vložení z Javy do Oracle

  2. Jak používat Database Documenter v Accessu

  3. Jaký datový typ MySQL použít pro ukládání booleovských hodnot

  4. Práce s daty JDBC mimo ASCII v Talendu