SQLite je oblíbená relační databáze, kterou vkládáte do své aplikace. Python přichází s oficiálními vazbami na SQLite. Tento článek zkoumá výhrady používání SQLite v Pythonu. Ukazuje problémy, které mohou způsobit různé verze propojených knihoven SQLite, jak datetime
objekty nejsou správně uloženy a jak musíte být zvlášť opatrní, když se spoléháte na Python with connection
kontextový manažer k potvrzení vašich dat.
Ú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 . Python oficiálně podporuje SQLite prostřednictvím vazeb (oficiální dokumenty). Práce s těmito vazbami však není vždy jednoduchá. Kromě obecných upozornění na SQLite, o kterých jsem hovořil dříve, existuje několik problémů specifických pro Python, které prozkoumáme v tomto článku .
Nekompatibilita verzí s cílem nasazení
Je zcela běžné, že vývojáři vytvářejí a testují kód na stroji, který se (velmi) liší od toho, kde je kód nasazen, pokud jde o operační systém (OS) a hardware. To způsobuje tři druhy problémů:
- Aplikace se chová odlišně kvůli rozdílům v operačním systému nebo hardwaru . Například můžete narazit na problémy s výkonem, když má cílový počítač méně paměti než váš počítač. Nebo může SQLite provádět některé operace na jednom OS pomaleji než na jiném, protože základní nízkoúrovňová rozhraní API OS, která používá, jsou odlišná.
- SQLite verze v cíli nasazení se liší od verze vývojářského počítače . To může způsobit problémy v obou směrech, protože v průběhu času přibývají nové funkce (a chování se mění), viz oficiální changelog. Například zastaralá nasazená verze SQLite může postrádat funkce, které ve vývoji fungovaly dobře. Novější verze SQLite v nasazení se také může chovat jinak než starší verze, kterou používáte na vývojovém počítači, např. když tým SQLite změní některé výchozí hodnoty.
- Buď Python vazby SQLite, nebo C knihovna mohou v cíli nasazení zcela chybět . Toto je Linux -problém specifický pro distribuci . Oficiální distribuce Windows a macOS budou obsahovat balíček verze knihovny SQLite C. V Linuxu je knihovna SQLite samostatný balíček. Pokud si Python zkompilujete sami, např. protože používáte Debian/Raspbian/atd. distribuce, která se dodává se starými verzemi funkcí, python
make
build skript vytvoří pouze vazby SQLite Pythonu if během procesu kompilace Pythonu byla detekována nainstalovaná knihovna SQLite C . Pokud provedete takovou re-kompilaci Pythonu sami, měli byste se ujistit, že nainstalovaná knihovna SQLite C je aktuální . To opět neplatí pro Debian atd. při instalaci SQLite přesapt
, takže možná budete muset sestavit a nainstalovat SQLite sami, předem k sestavení Pythonu.
Chcete-li zjistit, kterou verzi knihovny SQLite C používá váš interpret Pythonu, spusťte tento příkaz:
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
Nahrazení sqlite3.sqlite_version
s sqlite3.version
vám poskytne verzi vazeb SQLite v Pythonu .
Aktualizace základní knihovny SQLite C
Pokud chcete profitovat z funkcí nebo oprav chyb nejnovější verze SQLite, máte štěstí. Knihovna SQLite C je obvykle propojena za běhu a lze ji tedy nahradit bez jakýchkoli změn na vašem nainstalovaném interpretu Pythonu. Konkrétní kroky závisí na vašem OS (testováno pro Python 3.6+):
1) Windows: Stáhněte si předkompilované binární soubory x86 nebo x64 ze stránky stahování SQLite a nahraďte soubor sqlite3.dll
soubor nalezený v DLLs
složku vaší instalace Pythonu s tou, kterou jste si právě stáhli.
2) Linux: ze stránky stahování SQLite získejte autoconf zdroje, rozbalte archiv a spusťte ./configure && make && make install
který nainstaluje knihovnu do /usr/local/lib
ve výchozím nastavení.
Poté přidejte řádek export LD_LIBRARY_PATH=/usr/local/lib
na začátku skriptu shellu, který spouští váš skript Python, což nutí váš interpret Pythonu používat vlastní knihovnu.
3) macOS: z mé analýzy se zdá, že knihovna SQLite C je zkompilována do vazeb Pythonu binární (_sqlite3.cpython-36m-darwin.so
). Pokud jej chcete nahradit, budete pravděpodobně muset získat zdrojový kód Pythonu odpovídající vaší nainstalované instalaci Pythonu (např. 3.7.6
nebo jakoukoli verzi, kterou používáte). Zkompilujte Python ze zdroje pomocí skriptu sestavení macOS. Tento skript zahrnuje stahování a sestavení knihovny C SQLite, takže skript upravte tak, aby odkazoval na nejnovější verzi SQLite. Nakonec použijte kompilovaný soubor vazeb (např. _sqlite3.cpython-37m-darwin.so
), která nahradí zastaralou verzi.
Práce s časovým pásmem datetime
objekty
Většina vývojářů Pythonu obvykle používá datetime
objektů při práci s časovými razítky. Existují naivní datetime
objekty, které nevědí o svém časovém pásmu, a nenaivní ty, které jsou vědomé o časovém pásmu . Je dobře známo, že datetime
v Pythonu modul je nepředvídatelný, takže je obtížné dokonce vytvořit datetime.datetime
s ohledem na časové pásmo objektů. Například volání datetime.datetime.utcnow()
vytváří naivní objekt, což je kontraintuitivní pro vývojáře, kteří jsou s datetime
noví API, očekává se, že Python bude používat časové pásmo UTC! Tento úkol usnadňují knihovny třetích stran, jako je python-dateutil. Chcete-li vytvořit objekt s ohledem na časové pásmo, můžete použít kód, jako je tento:
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Bohužel, oficiální Python dokumentace sqlite3
modul je zavádějící, pokud jde o manipulaci s časovými razítky. Jak je popsáno zde, datetime
objekty jsou automaticky převedeny při použití PARSE_DECLTYPES
(a deklarovat TIMESTAMP
sloupec). I když je to technicky správné, konverze se ztratí časové pásmo informace ! V důsledku toho pokud skutečně používáte časové pásmo-aware datetime.datetime
objektů, musíte zaregistrovat své vlastní převodníky , které uchovávají informace o časovém pásmu, následovně:
def convert_timestamp_to_tzaware(timestamp: bytes) -> datetime.datetime:
# sqlite3 provides the timestamp as byte-string
return dateutil.parser.parse(timestamp.decode("utf-8"))
def convert_timestamp_to_sqlite(dt: datetime.datetime) -> str:
return dt.isoformat() # includes the timezone information at the end of the string
sqlite3.register_converter("timestamp", convert_timestamp_to_tzaware)
sqlite3.register_adapter(datetime.datetime, convert_timestamp_to_sqlite)
Code language: Python (python)
Jak můžete vidět, časové razítko je uloženo pouze jako TEXT
na konci. V SQLite neexistuje žádný skutečný datový typ „date“ nebo „datetime“.
Transakce a automatické potvrzení
Python sqlite3
modul automaticky nepotvrdí data, která jsou změněna vašimi dotazy . Když provádíte dotazy, které nějakým způsobem mění databázi, musíte buď vydat explicitní COMMIT
nebo použijete připojení jako kontextový správce objekt, jak ukazuje následující příklad:
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Jakmile byl výše uvedený blok opuštěn, sqlite3
implicitně volá connection.commit()
, ale pouze v případě, že transakce probíhá . Příkazy DML (Data Modification Language) automaticky zahájí transakci, ale dotazy zahrnující DROP
nebo CREATE
TABLE
/ INDEX
prohlášení ne, protože se podle dokumentace nepočítají jako DML. To je kontraintuitivní, protože tato prohlášení zjevně upravují data.
Tedy pokud spustíte jakýkoli DROP
nebo CREATE
TABLE
/ INDEX
příkazy uvnitř kontextového manažera, je dobrou praxí explicitně provést BEGIN TRANSACTION
nejprve prohlášení , takže správce kontextu skutečně zavolá connection.commit()
pro vás.
Zpracování 64bitových celých čísel
V předchozím článku jsem již hovořil o tom, že SQLite má problémy s velkými celými čísly, která jsou menší než -2^63
, nebo větší nebo rovno 2^63
. Pokud se je pokusíte použít v parametrech dotazu (s ?
symbol), sqlite3
Pythonu modul vyvolá OverflowError: Python int too large to convert to SQLite INTEGER
, která vás chrání před náhodnou ztrátou dat.
Chcete-li správně zpracovat velmi velká celá čísla, musíte:
- Použijte
TEXT
zadejte pro odpovídající sloupec tabulky a - Převeďte číslo na
str
již v Pythonu , než jej použijete jako parametr. - Převeďte řetězce zpět na
int
v Pythonu, kdyžSELECT
data
Závěr
Oficiální sqlite3
Pythonu modul je vynikající vazba na SQLite. Vývojáři noví v SQLite však musí pochopit, že existuje rozdíl mezi vazbami Pythonu a základní knihovnou SQLite C. Ve stínu číhá nebezpečí kvůli rozdílům ve verzích SQLite. K tomu může dojít, i když spustíte stejné Verze Pythonu na dvou různých počítačích, protože knihovna SQLite C může stále používat jinou verzi. Diskutoval jsem také o dalších problémech, jako je manipulace s objekty datetime a neustálá změna dat pomocí transakcí. Sám jsem o nich nevěděl, což způsobilo ztrátu dat uživatelům mých aplikací, takže doufám, že se vyhnete stejným chybám, jaké jsem udělal já.