sql >> Database teknologi >  >> RDS >> SQLite

Python og SQLite forbehold

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 via apt , 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:

  1. Brug TEXT type for den tilsvarende tabelkolonne, og
  2. Konverter nummeret til str allerede i Python , før du bruger det som en parameter.
  3. Konverter strengene tilbage til int i Python, når SELECT 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.


  1. Hvordan forespørger jeg mellem to datoer ved hjælp af MySQL?

  2. Sådan oprettes og manipuleres SQL-databaser med Python

  3. Udfør kommandoen Insert og returner indsat Id i Sql

  4. Oracle:sekvens MySequence.currval er endnu ikke defineret i denne session