SQLite er en populær relationel database, som du integrerer i din applikation. Python kommer med officielle bindinger til SQLite. Denne artikel undersøger forbehold ved brug af SQLite i Python. Det viser problemer, som forskellige versioner af sammenkædede SQLite-biblioteker kan forårsage, hvordan datetime
objekter er ikke gemt korrekt, og hvordan du skal være ekstra forsigtig, når du stoler på Pythons with connection
kontekstadministrator til at begå dine data.
Introduktion
SQLite er et populært relationsdatabasesystem (DB) . I modsætning til dets større, klient-serverbaserede brødre, såsom MySQL, kan SQLite indlejres i din applikation som et bibliotek . Python understøtter officielt SQLite via bindinger (officielle dokumenter). Det er dog ikke altid ligetil at arbejde med disse bindinger. Bortset fra de generiske SQLite advarsler, jeg diskuterede tidligere, er der flere Python-specifikke problemer, vi vil undersøge i denne artikel .
Versionsinkompatibilitet med implementeringsmål
Det er ret almindeligt, at udviklere opretter og tester kode på en maskine, der er (meget) anderledes end den, hvor koden er installeret, hvad angår operativsystem (OS) og hardware. Dette forårsager tre slags problemer:
- Applikationen opfører sig anderledes på grund af OS- eller hardwareforskelle . For eksempel kan du løbe ind i ydeevneproblemer, når målmaskinen har mindre hukommelse end din maskine. Eller SQLite kan udføre nogle operationer langsommere på ét OS end på andre, fordi de underliggende lavniveau OS API'er, det bruger, er anderledes.
- SQLite versionen på implementeringsmålet adskiller sig fra udviklermaskinens version . Dette kan give problemer i begge retninger, fordi nye funktioner tilføjes (og adfærd ændres) over tid, se den officielle ændringslog. For eksempel kan en forældet implementeret SQLite-version mangle funktioner, der fungerede fint under udviklingen. Også en nyere SQLite-version i udrulning opfører sig måske anderledes end en ældre version, du bruger på din udviklingsmaskine, f.eks. når SQLite-teamet ændrer nogle standardværdier.
- Enten mangler Python-bindingerne af SQLite eller C-biblioteket helt på implementeringsmålet . Dette er en Linux -distributionsspecifikt problem . Officielle Windows- og macOS-distributioner vil indeholde en bundtet version af SQLite C-biblioteket. På Linux er SQLite-biblioteket en separat pakke. Hvis du selv kompilerer Python, f.eks. fordi du bruger en Debian/Raspbian/etc. distribution, som leveres med ældgamle funktionsversioner, Python
make
build script vil kun bygge Pythons SQLite bindinger hvis et installeret SQLite C-bibliotek blev opdaget under Pythons kompileringsproces . Hvis du selv laver en sådan re-kompilering af Python, så skal du sørge for, at det installerede SQLite C-bibliotek er nyligt . Dette er igen ikke tilfældet for Debian osv., når du installerer SQLite viaapt
, så du skal muligvis også bygge og installere SQLite selv, før til at bygge Python.
For at finde ud af, hvilken version af SQLite C-biblioteket der bruges af din Python-fortolker, skal du køre denne kommando:
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
Erstatter sqlite3.sqlite_version
med sqlite3.version
vil give dig versionen af Pythons SQLite bindinger .
Opdatering af det underliggende SQLite C-bibliotek
Hvis du vil drage fordel af funktioner eller fejlrettelser i den seneste SQLite-version, er du heldig. SQLite C-biblioteket er typisk forbundet under kørsel og kan således erstattes uden ændringer i din installerede Python-fortolker. De konkrete trin afhænger af dit OS (testet til Python 3.6+):
1) Windows: Download de x86- eller x64-forkompilerede binære filer fra SQLite-downloadsiden og erstat sqlite3.dll
fil fundet i DLLs
mappe for din Python-installation med den, du lige har downloadet.
2) Linux: Hent autoconf fra SQLite-downloadsiden kilder, udpak arkivet og kør ./configure && make && make install
som vil installere biblioteket i /usr/local/lib
som standard.
Tilføj derefter linjen export LD_LIBRARY_PATH=/usr/local/lib
i begyndelsen af shell-scriptet, der starter dit Python-script, hvilket tvinger din Python-fortolker til at bruge det selvbyggede bibliotek.
3) macOS: fra min analyse ser det ud til, at SQLite C-biblioteket er kompileret i Python-bindingerne binær (_sqlite3.cpython-36m-darwin.so
). Hvis du vil erstatte det, skal du sandsynligvis få Python-kildekoden, der matcher din installerede Python-installation (f.eks. 3.7.6
eller hvilken version du bruger). Kompiler Python fra kilden ved hjælp af macOS build-scriptet. Dette script inkluderer download og opbygning af SQLites C-bibliotek, så sørg for at redigere scriptet for at referere til den seneste SQLite-version. Brug endelig den kompilerede bindingsfil (f.eks. _sqlite3.cpython-37m-darwin.so
), for at erstatte den forældede.
Arbejde med tidszonebevidst datetime
objekter
De fleste Python-udviklere bruger typisk datetime
objekter, når du arbejder med tidsstempler. Der er naive datetime
objekter, der ikke kender deres tidszone, og ikke-naive dem, som er tidszonebevidste . Det er velkendt, at Pythons datetime
modulet er skævt, hvilket gør det svært selv at oprette tidszonebevidste datetime.datetime
genstande. For eksempel kaldet datetime.datetime.utcnow()
opretter en naiv objekt, hvilket er kontraintuitivt for udviklere, der er nye til datetime
API'er, forventer, at Python bruger UTC-tidszonen! Tredjepartsbiblioteker, såsom python-dateutil, letter denne opgave. For at oprette et tidszone-bevidst objekt kan du bruge kode som denne:
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Desværre er den officielle Python-dokumentation af sqlite3
modul er vildledende, når det kommer til håndtering af tidsstempler. Som beskrevet her, datetime
objekter konverteres automatisk, når du bruger PARSE_DECLTYPES
(og erklære en TIMESTAMP
kolonne). Selvom dette er teknisk korrekt, vil konverteringen tabe tidszonen information ! Derfor, hvis du faktisk bruger tidszone-bevidst datetime.datetime
objekter, skal du registrere dine egne konvertere , som bevarer tidszoneoplysninger, som følger:
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)
Som du kan se, er tidsstemplet bare gemt som TEXT
til sidst. Der er ingen rigtig "dato" eller "datetime" datatype i SQLite.
Transaktioner og auto-commit
Pythons sqlite3
modul begår ikke automatisk data, der er ændret af dine forespørgsler . Når du udfører forespørgsler, der på en eller anden måde ændrer databasen, skal du enten udstede en eksplicit COMMIT
sætning, eller du bruger forbindelsen som kontekstadministrator objekt, som vist i følgende eksempel:
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Når ovenstående blok blev afsluttet, sqlite3
kalder implicit connection.commit()
, men gør det kun, hvis en transaktion er i gang . DML-udsagn (Data Modification Language) starter automatisk en transaktion, men forespørgsler, der involverer DROP
eller CREATE
TABLE
/ INDEX
udsagn gør det ikke, fordi de ifølge dokumentationen ikke tæller som DML. Dette er kontraintuitivt, fordi disse udsagn tydeligvis ændrer data.
Således, hvis du kører nogen DROP
eller CREATE
TABLE
/ INDEX
udsagn i kontekststyringen, er det god praksis eksplicit at udføre en BEGIN TRANSACTION
erklæring først , så kontekstadministratoren faktisk kalder connection.commit()
for dig.
Håndtering af 64-bit heltal
I en tidligere artikel har jeg allerede diskuteret, at SQLite har problemer med store heltal, der er mindre end -2^63
, eller større eller lig med 2^63
. Hvis du prøver at bruge dem i forespørgselsparametre (med ?
symbol), Pythons sqlite3
modul vil rejse en OverflowError: Python int too large to convert to SQLite INTEGER
, der beskytter dig mod utilsigtet tab af data.
For korrekt at håndtere meget store heltal skal du:
- Brug
TEXT
type for den tilsvarende tabelkolonne, og - Konverter nummeret til
str
allerede i Python , før du bruger det som en parameter. - Konverter strengene tilbage til
int
i Python, nårSELECT
ing data
Konklusion
Pythons officielle sqlite3
modul er en fremragende binding til SQLite. Udviklere, der er nye til SQLite, skal dog forstå, at der er en forskel mellem Python-bindingerne og det underliggende SQLite C-bibliotek. Der er fare på lur i skyggerne på grund af versionsforskelle af SQLite. Disse kan ske, selvom du kører det samme Python-version på to forskellige maskiner, fordi SQLite C-biblioteket muligvis stadig bruger en anden version. Jeg diskuterede også andre problemer såsom håndtering af datetime-objekter og vedvarende ændring af data ved hjælp af transaktioner. Jeg var ikke selv opmærksom på dem, hvilket forårsagede datatab for brugere af mine applikationer, så jeg håber, at du kan undgå de samme fejl, som jeg lavede.