SQLiteは、アプリケーションに埋め込む人気のあるリレーショナルデータベースです。 PythonにはSQLiteへの公式バインディングが付属しています。この記事では、PythonでSQLiteを使用する際の注意事項について説明します。リンクされたSQLiteライブラリのさまざまなバージョンが引き起こす可能性のある問題、datetime
オブジェクトが適切に保存されていないため、Pythonのwith connection
に依存する場合は特に注意が必要です。 データをコミットするコンテキストマネージャー。
はじめに
SQLiteは人気のあるリレーショナルデータベース(DB)システムです 。 MySQLなどのより大きなクライアントサーバーベースの兄弟とは異なり、SQLiteはライブラリとしてアプリケーションに埋め込むことができます 。 Pythonは、バインディングを介してSQLiteを公式にサポートしています(公式ドキュメント)。ただし、これらのバインディングの操作は必ずしも簡単ではありません。以前に説明した一般的なSQLiteの警告とは別に、この記事で検討するPython固有の問題がいくつかあります 。
バージョンとデプロイメントターゲットとの非互換性
開発者が、オペレーティングシステム(OS)とハードウェアの点で、コードが展開されているマシンとは(非常に)異なるマシンでコードを作成してテストすることは非常に一般的です。これにより、次の3種類の問題が発生します。
- アプリケーションはOSまたはハードウェアの違いにより動作が異なります 。たとえば、ターゲットマシンのメモリがマシンより少ない場合、パフォーマンスの問題が発生する可能性があります。または、SQLiteは、使用する基盤となる低レベルのOS APIが異なるため、あるOSでは他のOSよりも実行速度が遅い場合があります。
- SQLiteバージョン デプロイメントターゲットは、開発者のマシンのバージョンとは異なります 。これにより、双方向で問題が発生する可能性があります。時間の経過とともに新しい機能が追加される(および動作が変更される)ためです。公式の変更ログを参照してください。たとえば、古くなったデプロイ済みのSQLiteバージョンには、開発で正常に機能する機能がない場合があります。また、展開中の新しいSQLiteバージョンは、開発マシンで使用している古いバージョンとは異なる動作をする可能性があります。 SQLiteチームがいくつかのデフォルト値を変更したとき。
- SQLiteのPythonバインディング、またはCライブラリのいずれかが、デプロイメントターゲットで完全に欠落している可能性があります 。 これはLinux -配布固有の問題 。公式のWindowsおよびmacOSディストリビューションには、バンドルが含まれます。 SQLiteCライブラリのバージョン。 Linuxでは、SQLiteライブラリは別のパッケージです。 Pythonを自分でコンパイルする場合、たとえばDebian / Raspbian/etcを使用しているためです。古代の機能バージョン、 Python
make
に同梱されているディストリビューション ビルドスクリプトは、PythonのSQLiteバインディングをの場合にのみビルドします インストールされたSQLiteCライブラリがPythonのコンパイルプロセス中に検出されました 。このようなPythonの再コンパイルを自分で行う場合は、インストールされているSQLiteCライブラリが最近であることを確認する必要があります。 。これも、apt
を介してSQLiteをインストールする場合のDebianなどには当てはまりません。 、そのため、SQLiteを自分でビルドしてインストールする必要がある場合もあります。 Pythonの構築に。
Pythonインタープリターで使用されているSQLiteCライブラリのバージョンを確認するには、次のコマンドを実行します。
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
sqlite3.sqlite_version
を置き換える sqlite3.version
を使用 PythonのSQLiteバインディングのバージョンを提供します 。
基盤となるSQLiteCライブラリの更新
最新のSQLiteバージョンの機能やバグ修正から利益を得たい場合は、幸運です。 SQLite Cライブラリは通常、実行時にリンクされるため、インストールされているPythonインタープリターを変更せずに置き換えることができます。具体的な手順は、OSによって異なります(Python 3.6以降でテスト済み):
1)ウィンドウ: SQLiteダウンロードページからx86またはx64でプリコンパイルされたバイナリをダウンロードし、sqlite3.dll
を置き換えます。 DLLs
で見つかったファイル ダウンロードしたPythonインストールのフォルダ。
2)Linux: SQLiteダウンロードページからautoconfを取得します ソースを作成し、アーカイブを抽出して、./configure && make && make install
を実行します。 ライブラリを/usr/local/lib
にインストールします デフォルトでは。
次に、export LD_LIBRARY_PATH=/usr/local/lib
という行を追加します。 Pythonスクリプトを開始するシェルスクリプトの先頭で、Pythonインタープリターに自己構築ライブラリの使用を強制します。
3)macOS: 私の分析によると、SQLiteCライブラリはPythonのバインディングにコンパイルされているようです。 バイナリ(_sqlite3.cpython-36m-darwin.so
)。置き換える場合は、インストールされているPythonインストールに一致するPythonソースコードを取得する必要があります(例:3.7.6
または使用するバージョン)。 macOSビルドスクリプトを使用して、ソースからPythonをコンパイルします。このスクリプトにはSQLiteのCライブラリのダウンロードとビルドが含まれているため、スクリプトを編集して最新のSQLiteバージョンを参照するようにしてください。最後に、コンパイルされたバインディングファイルを使用します(例:_sqlite3.cpython-37m-darwin.so
)、古いものを置き換える。
タイムゾーン対応のdatetime
の操作 オブジェクト
ほとんどのPython開発者は通常、datetime
を使用します タイムスタンプを操作するときのオブジェクト。 ナイーブがあります datetime
タイムゾーンを知らないオブジェクト、およびナイーブでない タイムゾーンであるもの-認識 。 Pythonのdatetime
はよく知られています モジュールは風変わりで、タイムゾーン対応のdatetime.datetime
を作成することさえ困難です。 オブジェクト。たとえば、datetime.datetime.utcnow()
を呼び出します。 ナイーブを作成します オブジェクト。datetime
を初めて使用する開発者にとっては直感に反します。 API、PythonがUTCタイムゾーンを使用することを期待しています! python-dateutilなどのサードパーティライブラリは、このタスクを容易にします。タイムゾーン対応オブジェクトを作成するには、次のようなコードを使用できます。
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
残念ながら、sqlite3
の公式Pythonドキュメント モジュールは、タイムスタンプの処理に関して誤解を招く可能性があります。ここで説明するように、datetime
PARSE_DECLTYPES
を使用すると、オブジェクトは自動的に変換されます (そしてTIMESTAMP
を宣言します 桁)。これは技術的には正しいですが、変換は失われます タイムゾーン 情報 !したがって、実際にタイムゾーンを使用している場合-認識 datetime.datetime
オブジェクトの場合は、独自のコンバーターを登録する必要があります 、次のようにタイムゾーン情報を保持します:
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)
ご覧のとおり、タイムスタンプはTEXT
として保存されています。 最終的には。 SQLiteには実際の「date」または「datetime」データ型はありません。
Pythonのsqlite3
モジュールは、クエリによって変更されたデータを自動的にコミットしません 。データベースを何らかの形で変更するクエリを実行する場合は、明示的なCOMMIT
を発行する必要があります。 ステートメント、または接続をコンテキストマネージャーとして使用します 次の例に示すように、オブジェクト:
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
上記のブロックが終了したら、sqlite3
暗黙的にconnection.commit()
を呼び出します 、ただし、トランザクションが進行中の場合にのみそうします 。 DML(データ変更言語)ステートメントは自動的にトランザクションを開始しますが、DROP
を含むクエリ またはCREATE
TABLE
/ INDEX
ドキュメントによるとDMLとしてカウントされないため、ステートメントはカウントされません。これらのステートメントは明らかにデータを変更するため、これは直感に反します。
したがって、 DROP
を実行した場合 またはCREATE
TABLE
/ INDEX
コンテキストマネージャー内のステートメントでは、BEGIN TRANSACTION
を明示的に実行することをお勧めします 最初のステートメント 、コンテキストマネージャが実際にconnection.commit()
を呼び出すようにします あなたのために。
64ビット整数の処理
以前の記事で、SQLiteには-2^63
よりも小さい大きな整数の問題があることをすでに説明しました 、または2^63
以上 。それらをクエリパラメータで使用しようとした場合(?
を使用) シンボル)、Pythonのsqlite3
モジュールはOverflowError: Python int too large to convert to SQLite INTEGER
を発生させます 、偶発的なデータ損失からあなたを守ります。
非常に大きな整数を適切に処理するには、次のことを行う必要があります。
-
TEXT
を使用する 対応するテーブル列のおよびを入力します - 数値を
str
に変換します すでにPythonで 、パラメータとして使用する前に。 - 文字列を
int
に変換し直します Pythonでは、SELECT
ingデータ
結論
Pythonの公式sqlite3
モジュールはSQLiteへの優れたバインディングです。ただし、SQLiteを初めて使用する開発者は、Pythonバインディングと基盤となるSQLiteCライブラリに違いがあることを理解する必要があります。 SQLiteのバージョンの違いにより、影に潜む危険があります。これらは、同じを実行した場合でも発生する可能性があります SQLite Cライブラリがまだ異なるバージョンを使用している可能性があるため、2つの異なるマシンでのPythonバージョン。また、日時オブジェクトの処理やトランザクションを使用したデータの永続的な変更など、その他の問題についても説明しました。私自身はそれらに気づかなかったため、アプリケーションのユーザーのデータが失われたため、私が犯したのと同じ間違いを回避できることを願っています。