Hlavní navigace

SQLite - ultra lehké sql

4. 8. 2003
Doba čtení: 8 minut

Sdílet

Pokusím se v tomto článku přiblížit databázi SQLite, která je implementací jazyka SQL nad souborem dbm. Jedná se tedy o databázový stroj spadající do kategorie FoxPro, dbase nebo PC Fand. Na rozdíl od nich se ale nejedná o komplexní prostředí pro aplikace, ale skutečně jen o databázový stroj, nebo spíše stroječek :-).

Instalace SQLite probíhá klasickou kombinací „ ./configure && make && make install“, ale lze najít i binární balíčky pro nejrozšířenější platformy. V debianu, který používám, je součástí distribuce. Je třeba si dát pozor, aby verze byla novější než 2.1, protože od této verze je použit nový formát databáze, který není zpětně kompatibilní. Samotný program nevyžaduje žádné další knihovny a je distribuován jako public domain.

Nejprve se podívejme na základní vlastnosti. Je implementováno velmi mnoho ze standardu SQL92. Celá databáze je umístěna v jediném souboru na disku. Poskytuje podporu transakcí. Databázový soubor může být sdílen mezi různými počítači i platformami, osobně používám stejný soubor jak na Linuxu, tak na windows, ale je třeba vzít v souvislosti s češtinou do úvahy odlišná kódování znaků :-(. Nikde není žádný server, takže v okamžiku, kdy se s databází nepracuje, nemáte v systému žádné procesy navíc. Navíc je celá implementace velmi nenáročná na zdroje. Autoři uvádí, že knihovna pro C zabírá jen 25kb paměti. A skutečně i na mém postarším počítadle pracuje velmi svižně.

Základní knihovny dodávané s databází jsou určeny pro C/C++ a tcl. Je třeba si uvědomit, že obsahují kompletně soběstačný databázový server, takže po přilinkování k aplikaci je výsledek zcela nezávislý na přítomnosti dalšího software nebo knihoven. Dostupné jsou též knihovny pro další jazyky a překladače, jako je perl, python, delphi i kylix a mnoho dalších (viz Sqlite Wrappers). Osobně často používám DBI rozhraní pro perl bez nejmenších problémů.

Jako v každé databázi jsou zde výjimky v implementaci normy SQL a není jich bohužel málo. Klauzule check definující omezení sloupců je sice správně vyhodnocena, ale uplatní se pouze omezení not null a unique. Vnořené dotazy musí být statické, vyhodnoceny jsou jen jednou a nemohou odkazovat na pole hlavního dotazu. Cizí klíče jsou sice vyhodnoceny, ale nejsou vyžadovány restrikce, které by měly být uplatněny na jejich základě. Zjednodušena je i implementace trigerů, které nepodporují konstrukci „for each statement“, a trigery instead of jsou použitelné jen nad tabulkami, chybí také rekurze triggerů. Nelze provádět alter table a změny tabulek je nutné obcházet jejich přezaložením. Transakce jsou omezeny jen na jednu aktivní. Outer join může být jen ve formě left. View jsou jen pro čtení, což je ale na druhou stranu vyváženo možností definice triggerů pro obsluhu manipulací s daty nad view. Práva pro databázové objekty nejsou implementována, takže grand a revoke postrádají smysl, tady je ovšem otázkou, zda vůbec by měla nějaký smysl, vzhledem k tomu, že se jedná o jeden soubor, nikoliv o síťový server. A v souvislosti s češtinou je tu ještě jedna nevýhoda – neumí třídit česky.

Datové typy v SQLite nejsou. SQLite je „typelees“ a je deklarováno, že to je vlastnost, nikoliv chyba. Výsledkem je, že ve sloupci deklarovaném jako number můžete klidně ukládat slovo „ahoj“ a databáze to přechází jako samozřejmost. Ti, kdo znají perl, python nebo podobný jazyk, takové chování znají, ale u databáze může být trochu nečekané. Jedinou výjimkou je sloupec deklarovaný jako INTEGER PRIMARY KEY, kde je požadováno jednoznačné celé číslo. Jinak jsou data rozlišována jako numerická, nebo textová podle použití (popsáno v dokumentaci). Z toho vyplývá další nepříjemnost, a tou je poměrně často používaný údaj o datumu a o času, který je třeba kódovat do čísla ve vlastní režii. Jediným případem, kdy se databáze podívá, co by měl sloupec obsahovat, je při uplatnění klauzule order by v selectu.

S datovými typy úzce souvisí hodnota null a její reprezentace. SQLite plně podporuje hodnotu null v libovolném sloupci a chování je shodné s databázemi PostgreSQL nebo Oracle. V dokumentaci je popsán též způsob jejího vyhodnocování ve výrazech. Ovšem zejména méně zkušené je třeba varovat před jejím používáním, protože výskyt hodnoty null ve výrazech může vést k jejich ne zcela očekávaným výsledkům a je lépe se jí raději vyhnout. Nicméně zkušenější jsou na takové vyhodnocování zvyklí i z jiných databází a je přesně popsáno v dokumentaci.

Omezujícím limitem, pokud jde o velikost databáze, je 241 byte, což jsou dva terabyty. To je velikost na současných PC víceméně teoretická a v takových rozsazích je na místě zvážit nasazení skutečného, plnohodnotného databázového serveru. Starší formát měl omezení na dva gigabyte. Horší je limit omezující velikost jednoho řádku na 1 MB, čehož už není takový problém dosáhnout, a tento limit omezuje použití SQLite pro ukládání binárních dat do databáze. Je popsána úprava zdrojových textu před překompilací, která tento limit zvedne až na 16 MB. Do 1 MB se musí vejít i definice tabulky, to zamená příkaz create table. Počet řádků pro jednu tabulku je čtyři biliony, přičemž je to hodnota opět teoterická a sami autoři přiznávají, že nebyla nikdy testována. Stejně vypadá i limit pro počet tabulek a indexů. Jména objektů jsou bez limitu kromě jmen funkcí, kde je maximální délka 255 znaků. Jak je vidět, kromě nepříjemného omezení velikosti řádku jsou hodnoty pro nasazení SQLite dostačující a některé hodnoty jsou pro tento typ databáze spíše jen teoretické.

Rychlost je podle autorů velmi silnou stránkou databáze SQLite. Nebudu zastírat, že po této stránce nemám co říci z osobní zkušenosti. Pro mé použití byla rychlost vždy dostatečná, ale nepoužívám žádná objemná data. Raději shrnu, co uvádějí přímo autoři na stránce www.sqlite.or­g/speed.html. Tam zájemce může nalézt výsledky poměrně rozsáhlého testu databází PostgreSQL, MySQL a SQLite. SQLite je uváděna jak v normálním režimu, tak v ražimu nosync, kdy nevynucuje provedení skutečného zápisu na disk a spokojí se se systémovou vyrovnávací pamětí, kterou operační systém zapisuje až podle svého vytížení. Uvedu zde tedy malé shrnutí. Při vkládání velkého počtu záznamů do tabulky bez indexu s použitím transakcí byla SQLite v režimu nosync několikanásobně rychlejší, v bezpečnějším režimu sync byla sice pomalejší, ale pořád výrazně rychlejší než ostatní. Při použití indexu se odstup snížil proti mysql na minimum, ale stále byla SQLite nejrychlejší. Při operacích update a delete byly výsledky podobné jako u insertu. Při vybírání dat příkazem select se odstup snížil, ale stále byla SQLite nejrychlejší. Naproti tomu zcela opačné výsledky jsou uvedeny u změn v databázi, dvojnásobek času byl potřeba pro vytvoření indexu a stejně tak pro smazání tabulky. Vysokou rychlost operací bych připsal zajisté i jednoduchosti, absenci pokročilejšího zamykání, ale pro některé typy použití je rychlost a jednoduchost určitě podstatnější vlastností.

Klient sqlite představuje referenční implementaci. Jak již jsem zmínil, u knihoven pro programátory je pojem klient poněkud zavádějící, protože se nepřipojují k žádnému serveru, ale samy obsahují databázový stroj. Při spuštění je možné zadat jméno databázového souboru. Pokud neexistuje, bude založen. Dále je možné jako další parametr zadat řetězec k provedení. Pokud není zadán, objeví se příkazový řádek a je možné pokračovat interaktivně. Nápověda obsahující popis ovládacích příkazů se vypíše pomocí .help. Hotové scripty uložené v souborech na disku je možné též poslat na standardní vstup programu, což je také velmi užitečné.

Nyní nastal čas, podívat se na praktické použití. Nejdříve se spustí klient a založí databáze, pak již probíhá další komunikace na jeho vlastním příkazovém řádku.

sqlite test.db

Do sql příkazů je možné přidávat komentáře, a to v obou formách používaných v PL/SQL v Oracle (nevím, jak jinde).

/* komentář */
-- komentář do konce řádku

Vytvoření jednoduché databáze:

/* testovací tabulka */
create table test
   ( pole1 varchar(20)
   , pole2 number
   , pole3 date
   );

/* sem bude zapisovat trigger uvedený níže */
create table test1 (pole1 varchar);

/* jednoduchý index */
create unique index test_pole2 on test (pole2);

/* a ten výše zmíněný trigger */
create trigger trig_test after insert on test
begin
   insert into test1 (pole1) values (new.pole1);
end;

/* a stejně jednoduché view */
create view view_test as
   select test.pole2, test.pole1 from test, test1
    where test.pole1 = test1.pole1;

A nyní vložíme nějaká data:

insert into test (pole1, pole2, pole3)
   values ('abc', 1, '156');
insert into test (pole1, pole2, pole3)
   values ('ef', 2, '156');
insert into test (pole1, pole2, pole3)
   values ('gh', 3, '156');
insert into test (pole1, pole2, pole3)
   values ('gh', 'a', '156');

jak je vidět, datové typy nejsou kontrolovány a na posledním řádku klidně vložím hodnotu ‚a‘ do pole typu number.

Data nyní vypadají takto:

sqlite> select * from test;
abc|1|156
ef|2|156
gh|3|156
gh|a|156

sqlite> select * from test1;
abc
ef
gh
gh

sqlite> insert into test (pole1, pole2, pole3)
   values ('gh', 'a', '156');
SQL error: constraint failed

Z obsahu druhé tabulky je vidět, že trigger pracuje správně a také unikátní index se chová dle očekávání.

update test set pole2 = 5 where pole2 = 'a';
delete from test1 where pole1 like 'g%';

a opět se podíváme, jak data vypadají:

sqlite> select * from test;
abc|1|156
ef|2|156
gh|3|156
gh|5|156

sqlite> select * from test1;
abc
ef

a nakonec, co je vidět ve view:

sqlite> select * from view_test;
1|abc
2|ef

Tady bych svou velmi jednoduchou ukázku činnosti a syntaxe SQLite uzavřel, sice jsem zdaleka nevyčerpal možnosti SQLite, ale účelem bylo především ukázat, že oproti „standardu“ sql tu žádné větší překvapení v základní syntaxi nečíhá.

UX DAy - tip 2

Zkušenější jistě ocení celou řadu příkazů pragma, kterými lze ovlivňovat chování SQLite. Například je tu možnost nastavení velikosti cache, synchronizace diskových operací (viz odstavec o výkonosti). Dále je k dispozici příkaz explain, kterým je možné ladit optimálnost složitějších sql dotazů, příkaz copy pro přímý import dat z textového souboru a neméně užitečný příkaz .dump, kterým se databáze exportuje. Autoři uvádějí dokonce příklad použití příkazu dump pro přesun dat mezi SQLite a PosgreSQL jako demonstaci kompatibilnosti syntaxe v exportovaném souboru. Za zmínku stojí rovněž přítomnost pole rowid, použitelného například jako velmi efektivní parametr v podmínce where v dotazech, ale stejně jako u ostatních databází se může rowid při reorganizaci databáze měnit.

Na závěr si dovolím provést čistě subjektivní zhodnocení :-). Databáze SQLite mne velmi potěšila svým vysokým výkonem, nízkou náročností na systémové zdroje, poměrně kompletní implementací jazyka sql a jednoduchostí. Na druhou stranu mi citelně chybí přítomnost cizích klíčů a typové kontroly. Při nasazení je třeba zvážit všechny tyto věci a pro komplexní firemní data bych ji určitě nepoužil. Na druhé straně se mi zdá téměř ideální pro použití v malých aplikacích jako dobře strukturované úložiště dat místo vymýšlení vlastních formátů souborů nebo použití velkého SQL serveru. Pokud řešíte takové úkoly, SQLite určitě stojí za vyzkoušení.

Byl pro vás článek přínosný?