SQLite ist eine beliebte relationale Datenbank, die Sie in Ihre Anwendung einbetten. Python wird mit offiziellen Bindungen zu SQLite geliefert. Dieser Artikel untersucht die Vorbehalte bei der Verwendung von SQLite in Python. Es demonstriert Probleme, die verschiedene Versionen von verknüpften SQLite-Bibliotheken verursachen können, wie datetime
Objekte nicht richtig gespeichert werden und wie Sie besonders vorsichtig sein müssen, wenn Sie sich auf Pythons with connection
verlassen Kontextmanager, um Ihre Daten festzuschreiben.
Einführung
SQLite ist ein beliebtes relationales Datenbanksystem (DB) . Im Gegensatz zu seinen größeren Client-Server-basierten Brüdern wie MySQL kann SQLite als Bibliothek in Ihre Anwendung eingebettet werden . Python unterstützt SQLite offiziell über Bindungen (offizielle Dokumente). Die Arbeit mit diesen Bindungen ist jedoch nicht immer einfach. Abgesehen von den allgemeinen SQLite-Einschränkungen, die ich zuvor besprochen habe, gibt es mehrere Python-spezifische Probleme, die wir in diesem Artikel untersuchen werden .
Versionsinkompatibilitäten mit dem Bereitstellungsziel
Es ist durchaus üblich, dass Entwickler Code auf einem Computer erstellen und testen, der sich in Bezug auf Betriebssystem (OS) und Hardware (sehr) von dem unterscheidet, auf dem der Code bereitgestellt wird. Dies verursacht drei Arten von Problemen:
- Die Anwendung verhält sich aufgrund von Betriebssystem- oder Hardwareunterschieden anders . Beispielsweise können Leistungsprobleme auftreten, wenn der Zielcomputer über weniger Arbeitsspeicher verfügt als Ihr Computer. Oder SQLite führt möglicherweise einige Operationen auf einem Betriebssystem langsamer aus als auf anderen, da die zugrunde liegenden Low-Level-Betriebssystem-APIs, die es verwendet, unterschiedlich sind.
- Die SQLite-Version auf dem Bereitstellungsziel unterscheidet sich von der Version des Entwicklercomputers . Dies kann zu Problemen in beide Richtungen führen, da im Laufe der Zeit neue Funktionen hinzugefügt (und Verhaltensänderungen) werden, siehe offizielles Changelog. Einer veralteten bereitgestellten SQLite-Version können beispielsweise Funktionen fehlen, die in der Entwicklung einwandfrei funktionierten. Außerdem verhält sich eine neuere SQLite-Version in der Bereitstellung möglicherweise anders als eine ältere Version, die Sie auf Ihrem Entwicklungscomputer verwenden, z. wenn das SQLite-Team einige Standardwerte ändert.
- Entweder die Python-Bindungen von SQLite oder die C-Bibliothek fehlen möglicherweise vollständig auf dem Bereitstellungsziel . Dies ist ein Linux -verteilungsspezifisches Problem . Offizielle Windows- und macOS-Distributionen enthalten eine gebündelte Version der SQLite C-Bibliothek. Unter Linux ist die SQLite-Bibliothek ein separates Paket. Wenn Sie Python selbst kompilieren, z. weil du ein Debian/Raspbian/etc. -Distribution, die mit alten Funktionsversionen ausgeliefert wird, das Python
make
Das Build-Skript erstellt die SQLite-Bindungen von Python nur wenn Während des Kompilierungsprozesses von Python wurde eine installierte SQLite-C-Bibliothek erkannt . Wenn Sie selbst eine solche Neukompilierung von Python durchführen, sollten Sie sicherstellen, dass die installierte SQLite-C-Bibliothek aktuell ist . Dies ist wiederum nicht der Fall für Debian etc., wenn SQLite überapt
installiert wird , daher müssen Sie möglicherweise vorher SQLite selbst erstellen und installieren zum Erstellen von Python.
Führen Sie diesen Befehl aus, um herauszufinden, welche Version der SQLite-C-Bibliothek von Ihrem Python-Interpreter verwendet wird:
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
sqlite3.sqlite_version
ersetzen mit sqlite3.version
gibt Ihnen die Version der SQLite-Bindungen von Python .
Aktualisieren der zugrunde liegenden SQLite-C-Bibliothek
Wenn Sie von Funktionen oder Fehlerbehebungen der neuesten SQLite-Version profitieren möchten, haben Sie Glück. Die SQLite-C-Bibliothek wird normalerweise zur Laufzeit gelinkt und kann daher ohne Änderungen an Ihrem installierten Python-Interpreter ersetzt werden. Die konkreten Schritte hängen von Ihrem Betriebssystem ab (getestet für Python 3.6+):
1) Windows: Laden Sie die vorkompilierten x86- oder x64-Binärdateien von der SQLite-Downloadseite herunter und ersetzen Sie die sqlite3.dll
Datei in den DLLs
gefunden Ordner Ihrer Python-Installation mit dem Ordner, den Sie gerade heruntergeladen haben.
2) Linux: Holen Sie sich von der SQLite-Downloadseite die autoconf Quellen, extrahieren Sie das Archiv und führen Sie ./configure && make && make install
aus wodurch die Bibliothek in /usr/local/lib
installiert wird standardmäßig.
Fügen Sie dann die Zeile export LD_LIBRARY_PATH=/usr/local/lib
hinzu am Anfang des Shell-Skripts, das Ihr Python-Skript startet, wodurch Ihr Python-Interpreter gezwungen wird, die selbst erstellte Bibliothek zu verwenden.
3) macOS: Nach meiner Analyse scheint die SQLite-C-Bibliothek in die Python-Bindungen kompiliert zu sein binär (_sqlite3.cpython-36m-darwin.so
). Wenn Sie es ersetzen möchten, müssen Sie wahrscheinlich den Python-Quellcode abrufen, der zu Ihrer installierten Python-Installation passt (z. B. 3.7.6
oder welche Version Sie verwenden). Kompilieren Sie Python aus der Quelle mit dem macOS-Build-Skript. Dieses Skript umfasst das Herunterladen und Erstellen der C-Bibliothek von SQLite. Stellen Sie also sicher, dass Sie das Skript so bearbeiten, dass es auf die neueste SQLite-Version verweist. Verwenden Sie schließlich die kompilierte Bindungsdatei (z. B. _sqlite3.cpython-37m-darwin.so
), um das veraltete zu ersetzen.
Arbeiten mit zeitzonensensitivem datetime
Objekte
Die meisten Python-Entwickler verwenden normalerweise datetime
Objekte beim Arbeiten mit Zeitstempeln. Es gibt Naive datetime
Objekte, die ihre Zeitzone nicht kennen, und nicht naiv diejenigen, die Zeitzonen bewusst sind . Es ist bekannt, dass Pythons datetime
-Modul ist skurril, was es sogar schwierig macht, zeitzonenbewusstes datetime.datetime
zu erstellen Objekte. Zum Beispiel der Aufruf datetime.datetime.utcnow()
erzeugt ein naives -Objekt, was für Entwickler, die mit datetime
noch nicht vertraut sind, kontraintuitiv ist APIs, die erwarten, dass Python die UTC-Zeitzone verwendet! Bibliotheken von Drittanbietern wie python-dateutil erleichtern diese Aufgabe. Um ein zeitzonenfähiges Objekt zu erstellen, können Sie Code wie diesen verwenden:
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Leider ist die offizielle Python-Dokumentation der sqlite3
-Modul ist irreführend, wenn es um die Handhabung von Zeitstempeln geht. Wie hier beschrieben, datetime
Objekte werden automatisch konvertiert, wenn PARSE_DECLTYPES
verwendet wird (und Deklaration eines TIMESTAMP
Säule). Obwohl dies technisch korrekt ist, wird die Konvertierung verloren die Zeitzone Informationen ! Folglich, wenn Sie tatsächlich die Zeitzone verwenden, aware datetime.datetime
-Objekte müssen Sie Ihre eigenen Konverter registrieren , die Zeitzoneninformationen enthalten, wie folgt:
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)
Wie Sie sehen können, wird der Zeitstempel nur als TEXT
gespeichert letzten Endes. In SQLite gibt es keinen echten „date“- oder „datetime“-Datentyp.
Transaktionen und Autocommit
Pythons sqlite3
-Modul schreibt Daten, die durch Ihre Abfragen geändert wurden, nicht automatisch fest . Wenn Sie Abfragen ausführen, die irgendwie die Datenbank ändern, müssen Sie entweder ein explizites COMMIT
ausgeben -Anweisung, oder Sie verwenden die Verbindung als Kontextmanager Objekt, wie im folgenden Beispiel gezeigt:
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Sobald der obige Block verlassen wurde, sqlite3
ruft implizit connection.commit()
auf , aber nur, wenn eine Transaktion läuft . DML-Anweisungen (Data Modification Language) starten automatisch eine Transaktion, Abfragen jedoch mit DROP
oder CREATE
TABLE
/ INDEX
-Anweisungen nicht, da sie laut Dokumentation nicht als DML gelten. Dies widerspricht der Intuition, da diese Anweisungen eindeutig Daten modifizieren.
Also, wenn Sie DROP
ausführen oder CREATE
TABLE
/ INDEX
Anweisungen innerhalb des Kontextmanagers, ist es eine gute Praxis, explizit eine BEGIN TRANSACTION
auszuführen Aussage zuerst , sodass der Kontextmanager tatsächlich connection.commit()
aufruft für dich.
Handhabung von 64-Bit-Ganzzahlen
In einem früheren Artikel habe ich bereits darauf hingewiesen, dass SQLite Probleme mit großen Ganzzahlen hat, die kleiner als -2^63
sind , oder größer oder gleich 2^63
. Wenn Sie versuchen, sie in Abfrageparametern zu verwenden (mit dem ?
symbol), Pythons sqlite3
-Modul löst einen OverflowError: Python int too large to convert to SQLite INTEGER
aus , um Sie vor versehentlichem Datenverlust zu schützen.
Um sehr große Ganzzahlen richtig zu handhaben, müssen Sie:
- Verwenden Sie den
TEXT
Geben Sie für die entsprechende Tabellenspalte und ein - Wandle die Zahl in
str
um bereits in Python , bevor Sie es als Parameter verwenden. - Wandle die Strings zurück in
int
in Python, wennSELECT
daten
Schlussfolgerung
Pythons offizielles sqlite3
Modul ist eine hervorragende Bindung an SQLite. Entwickler, die neu bei SQLite sind, müssen jedoch verstehen, dass es einen Unterschied zwischen den Python-Bindungen und der zugrunde liegenden SQLite-C-Bibliothek gibt. Aufgrund von Versionsunterschieden von SQLite lauert eine Gefahr im Schatten. Dies kann passieren, selbst wenn Sie dasselbe ausführen Python-Version auf zwei verschiedenen Computern, da die SQLite-C-Bibliothek möglicherweise noch eine andere Version verwendet. Ich habe auch andere Probleme besprochen, wie z. B. den Umgang mit Datetime-Objekten und das dauerhafte Ändern von Daten mithilfe von Transaktionen. Ich war mir ihrer selbst nicht bewusst, was zu Datenverlusten bei Benutzern meiner Anwendungen führte, daher hoffe ich, dass Sie die gleichen Fehler vermeiden können, die ich gemacht habe.