SQLite adalah database relasional populer yang Anda sematkan ke dalam aplikasi Anda. Python hadir dengan binding resmi ke SQLite. Artikel ini membahas peringatan penggunaan SQLite dengan Python. Ini menunjukkan masalah yang dapat disebabkan oleh versi berbeda dari pustaka SQLite tertaut, bagaimana datetime
objek tidak disimpan dengan benar, dan bagaimana Anda harus ekstra hati-hati saat mengandalkan with connection
Python pengelola konteks untuk mengkomit data Anda.
Pengantar
SQLite adalah sistem database relasional (DB) yang populer . Tidak seperti saudaranya yang lebih besar, berbasis client-server, seperti MySQL, SQLite dapat disematkan ke dalam aplikasi Anda sebagai perpustakaan . Python secara resmi mendukung SQLite melalui binding (dokumen resmi). Namun, bekerja dengan binding tersebut tidak selalu mudah. Terlepas dari peringatan SQLite umum yang saya bahas sebelumnya, ada beberapa masalah khusus Python yang akan kita periksa dalam artikel ini .
Versi tidak kompatibel dengan target penerapan
Sangat umum bahwa pengembang membuat dan menguji kode pada mesin yang (sangat) berbeda dengan mesin tempat kode disebarkan, dalam hal sistem operasi (OS) dan perangkat keras. Ini menyebabkan tiga jenis masalah:
- Aplikasi berperilaku berbeda karena perbedaan OS atau perangkat keras . Misalnya, Anda mungkin mengalami masalah kinerja ketika mesin target memiliki lebih sedikit memori daripada mesin Anda. Atau SQLite mungkin menjalankan beberapa operasi lebih lambat pada satu OS daripada yang lain, karena API OS tingkat rendah yang digunakannya berbeda.
- Versi SQLite pada target penerapan berbeda dari versi mesin pengembang . Ini dapat menyebabkan masalah di kedua arah, karena fitur baru ditambahkan (dan perubahan perilaku) dari waktu ke waktu, lihat log perubahan resmi. Misalnya, versi SQLite yang sudah ketinggalan zaman mungkin kekurangan fitur yang berfungsi dengan baik dalam pengembangan. Selain itu, versi SQLite yang lebih baru dalam penerapan mungkin berperilaku berbeda dari versi lama yang Anda gunakan pada mesin pengembangan Anda, mis. saat tim SQLite mengubah beberapa nilai default.
- Baik Ikatan Python dari SQLite, atau pustaka C, mungkin hilang seluruhnya pada target penerapan . Ini adalah Linux -masalah khusus distribusi . Distribusi resmi Windows dan macOS akan berisi bundel versi pustaka SQLite C. Di Linux, perpustakaan SQLite adalah paket terpisah. Jika Anda mengkompilasi Python sendiri, mis. karena Anda menggunakan Debian/Raspbian/dll. distribusi yang dikirimkan dengan versi fitur kuno, the Python
make
skrip build hanya akan membuat binding SQLite Python jika pustaka SQLite C yang terinstal terdeteksi selama proses kompilasi Python . Jika Anda melakukan kompilasi ulang Python sendiri, maka Anda harus memastikan bahwa pustaka SQLite C yang diinstal terbaru . Ini, sekali lagi, tidak berlaku untuk Debian dll. ketika menginstal SQLite melaluiapt
, jadi Anda mungkin juga harus membuat dan menginstal SQLite sendiri, sebelumnya untuk membangun Python.
Untuk mengetahui versi library SQLite C yang digunakan oleh interpreter Python Anda, jalankan perintah ini:
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
Mengganti sqlite3.sqlite_version
dengan sqlite3.version
akan memberi Anda versi binding SQLite Python .
Memperbarui pustaka SQLite C yang mendasarinya
Jika Anda ingin mendapatkan keuntungan dari fitur atau perbaikan bug dari versi SQLite terbaru, Anda beruntung. Pustaka SQLite C biasanya ditautkan saat run-time dan dengan demikian dapat diganti tanpa perubahan apa pun pada juru bahasa Python yang Anda instal. Langkah konkretnya bergantung pada OS Anda (diuji untuk Python 3.6+):
1) Jendela: Unduh binari prakompilasi x86 atau x64 dari halaman unduhan SQLite dan ganti sqlite3.dll
file ditemukan di DLLs
folder instalasi Python Anda dengan yang baru saja Anda unduh.
2) Linux: dari halaman unduhan SQLite, dapatkan autoconf sumber, ekstrak arsip, dan jalankan ./configure && make && make install
yang akan menginstal perpustakaan ke /usr/local/lib
secara default.
Kemudian tambahkan baris export LD_LIBRARY_PATH=/usr/local/lib
di awal skrip shell yang memulai skrip Python Anda, yang memaksa juru bahasa Python Anda untuk menggunakan pustaka yang dibuat sendiri.
3) macOS: dari analisis saya, tampaknya pustaka SQLite C dikompilasi ke dalam binding Python biner (_sqlite3.cpython-36m-darwin.so
). Jika Anda ingin menggantinya, Anda mungkin perlu mendapatkan kode sumber Python yang cocok dengan instalasi Python yang Anda instal (mis. 3.7.6
atau versi apa pun yang Anda gunakan). Kompilasi Python dari sumber, menggunakan skrip build macOS. Skrip ini mencakup pengunduhan dan pembuatan pustaka C SQLite, jadi pastikan untuk mengedit skrip untuk merujuk ke versi SQLite terbaru. Terakhir, gunakan file binding yang dikompilasi (mis. _sqlite3.cpython-37m-darwin.so
), untuk menggantikan yang lama.
Bekerja dengan datetime
yang sadar zona waktu objek
Kebanyakan pengembang Python biasanya menggunakan datetime
objek saat bekerja dengan cap waktu. Ada yang naif datetime
objek yang tidak tahu tentang zona waktunya, dan non-naif yang, yang sadarzone zona waktu . Sudah diketahui bahwa datetime
Python modul unik, sehingga sulit untuk membuat datetime.datetime
yang peka terhadap zona waktu objek. Misalnya, panggilan datetime.datetime.utcnow()
menciptakan naif objek, yang kontra-intuitif untuk pengembang yang baru mengenal datetime
API, mengharapkan Python menggunakan zona waktu UTC! Pustaka pihak ketiga, seperti python-dateutil, memfasilitasi tugas ini. Untuk membuat objek yang sadar zona waktu, Anda dapat menggunakan kode seperti ini:
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Sayangnya, dokumentasi resmi Python dari sqlite3
modul menyesatkan dalam hal penanganan cap waktu. Seperti yang dijelaskan di sini, datetime
objek secara otomatis dikonversi saat menggunakan PARSE_DECLTYPES
(dan mendeklarasikan TIMESTAMP
kolom). Meskipun secara teknis ini benar, konversi akan kehilangan zona waktu informasi ! Akibatnya, jika Anda benar-benar menggunakan zona waktu-sadar datetime.datetime
objek, Anda harus mendaftarkan konverter Anda sendiri , yang menyimpan informasi zona waktu, sebagai berikut:
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)
Seperti yang Anda lihat, stempel waktu hanya disimpan sebagai TEXT
pada akhirnya. Tidak ada tipe data “date” atau “datetime” yang sebenarnya di SQLite.
Transaksi dan komit otomatis
sqlite3
Python modul tidak secara otomatis mengkomit data yang dimodifikasi oleh kueri Anda . Saat Anda melakukan kueri yang entah bagaimana mengubah database, Anda juga harus mengeluarkan COMMIT
explicit eksplisit pernyataan, atau Anda menggunakan koneksi sebagai pengelola konteks objek, seperti yang ditunjukkan pada contoh berikut:
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Setelah blok di atas keluar, sqlite3
secara implisit memanggil connection.commit()
, tetapi hanya melakukannya jika transaksi sedang berlangsung . Pernyataan DML (Bahasa Modifikasi Data) secara otomatis memulai transaksi, tetapi kueri yang melibatkan DROP
atau CREATE
TABLE
/ INDEX
pernyataan tidak, karena tidak dihitung sebagai DML menurut dokumentasi. Ini kontra-intuitif, karena pernyataan ini jelas mengubah data.
Jadi, jika Anda menjalankan DROP
atau CREATE
TABLE
/ INDEX
pernyataan di dalam pengelola konteks, praktik yang baik adalah menjalankan BEGIN TRANSACTION
. secara eksplisit pernyataan terlebih dahulu , sehingga pengelola konteks akan benar-benar memanggil connection.commit()
untukmu.
Menangani bilangan bulat 64-bit
Pada artikel sebelumnya saya sudah membahas bahwa SQLite memiliki masalah dengan bilangan bulat besar yang lebih kecil dari -2^63
, atau lebih besar atau sama dengan 2^63
. Jika Anda mencoba menggunakannya dalam parameter kueri (dengan ?
simbol), sqlite3
Python modul akan memunculkan OverflowError: Python int too large to convert to SQLite INTEGER
, melindungi Anda dari kehilangan data yang tidak disengaja.
Untuk menangani bilangan bulat yang sangat besar dengan benar, Anda harus:
- Gunakan
TEXT
ketik untuk kolom tabel yang sesuai, dan - Ubah bilangan menjadi
str
sudah menggunakan Python , sebelum menggunakannya sebagai parameter. - Konversi string kembali ke
int
dengan Python, ketikaSELECT
memasukkan data
Kesimpulan
sqlite3
resmi Python module adalah pengikat yang sangat baik untuk SQLite. Namun, pengembang yang baru mengenal SQLite perlu memahami bahwa ada perbedaan antara binding Python dan pustaka SQLite C yang mendasarinya. Ada bahaya yang mengintai, karena perbedaan versi SQLite. Ini dapat terjadi bahkan jika Anda menjalankan sama Versi python pada dua mesin yang berbeda, karena library SQLite C mungkin masih menggunakan versi yang berbeda. Saya juga membahas masalah lain seperti menangani objek datetime dan terus-menerus mengubah data menggunakan transaksi. Saya sendiri tidak menyadarinya, yang menyebabkan hilangnya data bagi pengguna aplikasi saya, jadi saya harap Anda dapat menghindari kesalahan yang sama yang saya buat.