Pythonsqlite3と並行性


87

「スレッド」モジュールを使用するPythonプログラムがあります。私のプログラムは毎秒1回、Webからデータをフェッチする新しいスレッドを開始し、このデータをハードドライブに保存します。sqlite3を使用してこれらの結果を保存したいのですが、機能させることができません。問題は次の行に関するもののようです。

conn = sqlite3.connect("mydatabase.db")
  • このコード行を各スレッド内に配置すると、データベースファイルがロックされていることを示すOperationalErrorが発生します。これは、別のスレッドがsqlite3接続を介してmydatabase.dbを開いており、それをロックしていることを意味していると思います。
  • このコード行をメインプログラムに配置し、接続オブジェクト(conn)を各スレッドに渡すと、ProgrammingErrorが発生し、スレッドで作成されたSQLiteオブジェクトは同じスレッドでのみ使用できるというメッセージが表示されます。

以前は、すべての結果をCSVファイルに保存していましたが、これらのファイルロックの問題はありませんでした。うまくいけば、これはsqliteで可能になります。何か案は?


5
Pythonの最近のバージョンには、この問題を修正する新しいバージョンのsqlite3が含まれていることに注意してください。
Ryan Fugger 2012年

@RyanFuggerこれをサポートする最も古いバージョンが何であるか知っていますか?私は2.7を使用しています
notbad.jpeg 2013年

@RyanFugger AFAIKには、それが修正された新しいバージョンのSQLite3を含むビルド済みバージョンはありません。ただし、自分で作成することはできます。
shezi 2013年

回答:


44

消費者/生産者パターンを使用できます。たとえば、スレッド間で共有されるキューを作成できます。Webからデータをフェッチする最初のスレッドは、このデータを共有キューにエンキューします。データベース接続を所有する別のスレッドは、データをキューからデキューし、データベースに渡します。


8
FWIW:sqliteの新しいバージョンでは、スレッド間(カーソルを除く)で接続とオブジェクトを共有できると主張していますが、実際にはそうではないことがわかりました。
Richard Levasseur

これは、EvgenyLazinが上で述べたことの例です。
dugres 2009年

4
共有キューの背後にデータベースを隠すことは、この質問に対する本当に悪い解決策です。SQL一般とSQLiteには、アドホックに自分で構築できるものよりもはるかに洗練されたロックメカニズムがすでに組み込まれいるためです。
shezi 2013年

1
質問を読む必要があります。その時点では、組み込みのロックメカニズムはありませんでした。多くの最新の組み込みデータベースには、パフォーマンス上の理由からこのメカニズムがありません(例:LevelDB)。
Evgeny Lazin 2013年

180

人気の信念に反して、sqlite3のの新しいバージョンが行う複数のスレッドからの支援へのアクセスを。

これは、オプションのキーワード引数を介して有効にできますcheck_same_thread

sqlite.connect(":memory:", check_same_thread=False)

4
予測できない例外が発生し、このオプションを使用するとPythonでさえクラッシュします(Windows32のPython2.7)。
reclosedev 2012年

4
ドキュメントによると、マルチスレッドモードでは、単一のデータベース接続を複数のスレッドで使用することはできません。シリアル化されたモードもあります
Casebash 2013年

1
気にしないで、見つけただけです:http
Medeiros 2013

1
@FrEaKmAn、申し訳ありませんが、それはずっと前のことであり、:memory:データベースでもありません。その後、複数のスレッドでsqlite接続を共有しませんでした。
reclosedev 2014

2
@FrEaKmAn、マルチスレッドアクセスでのPythonプロセスのコアダンプでこれに遭遇しました。動作は予測できず、例外はログに記録されませんでした。私が正しく覚えていれば、これは読み取りと書き込みの両方に当てはまりました。これは私がこれまで実際にPythonをクラッシュさせるのを見た1つのことです:D。スレッドセーフモードでコンパイルされたsqliteでこれを試したことはありませんが、当時、システムのデフォルトのsqliteを再コンパイルする自由はありませんでした。私は結局、エリックが提案したものに似た何かをして、スレッドの互換性を無効にしました
2015

17

以下はmail.python.org.pipermail.1239789で 見つかりました

私は解決策を見つけました。Pythonのドキュメントにこのオプションについて一言も書かれていない理由がわかりません。したがって、接続関数に新しいキーワード引数を追加する必要があり、別のスレッドでそれからカーソルを作成できるようになります。したがって、以下を使用します。

sqlite.connect(":memory:", check_same_thread = False)

私にとっては完璧にうまくいきます。もちろん、これからは、データベースへの安全なマルチスレッドアクセスを処理する必要があります。とにかく助けようとしてすべてのthx。


(GILを使用すると、データベースへの真のマルチスレッドアクセスの邪魔になるものはほとんどありません)
Erik Aronesty 2018

WARNING:Pythonのドキュメントを持っている。このについて言いたいcheck_same_threadオプション:「同じ接続の書き込み操作で複数のスレッドを用いたものを避けるのデータが破損するユーザーによってシリアル化する必要があります。」そうです、コードが常に1つのスレッドだけがデータベースに書き込むことができることを保証する限り、複数のスレッドでSQLiteを使用できます。そうでない場合は、データベースが破損する可能性があります。
Ajedi32

14

マルチプロセッシングに切り替えます。それははるかに優れており、拡張性が高く、複数のCPUを使用することで複数のコアの使用を超えることができ、インターフェイスはPythonスレッドモジュールを使用する場合と同じです。

または、Aliが提案したように、SQLAlchemyのスレッドプールメカニズムを使用するだけです。それはあなたのためにすべてを自動的に処理し、それらのいくつかを引用するだけで、多くの追加機能を備えています:

  1. SQLAlchemyには、SQLite、Postgres、MySQL、Oracle、MS-SQL、Firebird、MaxDB、MS Access、Sybase、Informixのダイアレクトが含まれています。IBMはDB2ドライバーもリリースしました。したがって、SQLiteから移行することにした場合でも、アプリケーションを書き直す必要はありません。
  2. SQLAlchemyのオブジェクトリレーショナルマッパー(ORM)の中心部分であるUnit Of Workシステムは、保留中の作成/挿入/更新/削除操作をキューに編成し、それらすべてを1つのバッチでフラッシュします。これを実現するために、キュー内のすべての変更されたアイテムのトポロジ的な「依存関係の並べ替え」を実行して、外部キーの制約を尊重し、冗長なステートメントをグループ化して、さらにバッチ処理できる場合があります。これにより、最大の効率とトランザクションの安全性が実現し、デッドロックの可能性が最小限に抑えられます。

12

このためにスレッドを使用するべきではありません。これはツイストにとっては些細な作業であり、とにかくかなり先に進む可能性があります。

スレッドを1つだけ使用し、リクエストが完了すると、書き込みを行うイベントがトリガーされます。

ツイストはあなたのためにスケジューリング、コールバックなどの面倒を見ます。それはあなたの文字列として全体の結果を手でしょうか、(私が持っているストリームプロセッサを介してそれを実行することができ、TwitterのAPIのFriendFeed APIを結果として発信者にイベントオフ両方の火がまだダウンロードされていること)。

データをどのように処理しているかに応じて、結果全体をsqliteに完全にダンプするか、クックしてダンプするか、読み取り中にクックして最後にダンプすることができます。

私はあなたがgithubで望んでいることに近い何かをする非常に単純なアプリケーションを持っています。私はそれをpfetch(並列フェッチ)と呼んでいます。スケジュールに従ってさまざまなページを取得し、結果をファイルにストリーミングし、オプションで各ページが正常に完了するとスクリプトを実行します。また、条件付きGETのようないくつかの凝ったことも行いますが、それでもあなたがしていることの良い基盤になる可能性があります。


7

または、私のように怠け者の場合は、SQLAlchemyを使用できます。それはあなたのためにスレッド化を処理し(スレッドローカルといくつかの接続プールを使用して)、それを行う方法は構成可能です。

追加のボーナスとして、並行アプリケーションにSqliteを使用することが災害になると認識/決定した場合、MySQLやPostgresなどを使用するようにコードを変更する必要はありません。切り替えるだけです。


1
公式ウェブサイトのどこにもPythonのバージョンが指定されていないのはなぜですか?
表示名

3

このエラーの原因となるマルチスレッドで同じカーソルを使用せずに、同じスレッドで同じカーソルを使用するには、データベースへのすべてのトランザクションのsession.close()後に使用する必要があります。



0

私はEvgenyの答えが好きです-キューは一般的にスレッド間通信を実装するための最良の方法です。完全を期すために、他のいくつかのオプションがあります。

  • 生成されたスレッドがDB接続の使用を終了したら、DB接続を閉じます。これで問題は解決しますがOperationalError、パフォーマンスのオーバーヘッドのため、このような接続の開閉は通常、いいえ、いいえです。
  • 子スレッドは使用しないでください。1秒に1回のタスクが適度に軽量である場合は、フェッチと保存を実行してから、適切なタイミングまでスリープ状態にすることができます。フェッチとストアの操作に1秒以上かかる可能性があるため、これは望ましくありません。また、マルチスレッドアプローチでは、多重化されたリソースのメリットが失われます。

0

プログラムの並行性を設計する必要があります。SQLiteには明確な制限があり、それに従う必要があります。FAQ(次の質問も参照)を参照してください


0

Scrapyは私の質問に対する潜在的な答えのようです。そのホームページは私の正確な仕事を説明しています。(コードがどれほど安定しているかはまだわかりませんが。)


0

私は、データの永続性のためにy_serial Pythonモジュールを見てみましょう:http://yserial.sourceforge.net

これは、単一のSQLiteデータベースを取り巻くデッドロックの問題を処理します。同時実行性への要求が重くなる場合は、多くのデータベースのクラスFarmを簡単に設定して、確率的な時間にわたって負荷を分散させることができます。

これがプロジェクトに役立つことを願っています... 10分で実装できるほど簡単なはずです。


0

上記の回答のいずれにもベンチマークが見つからなかったため、すべてをベンチマークするためのテストを作成しました。

3つのアプローチを試しました

  1. SQLiteデータベースからの順次読み取りと書き込み
  2. ThreadPoolExecutorを使用した読み取り/書き込み
  3. ProcessPoolExecutorを使用した読み取り/書き込み

ベンチマークの結果とポイントは次のとおりです。

  1. シーケンシャル読み取り/シーケンシャル書き込みが最適です
  2. 並行して処理する必要がある場合は、ProcessPoolExecutorを使用して並行して読み取ります
  3. ThreadPoolExecutorまたはProcessPoolExecutorを使用して書き込みを実行しないでください。データベースロックエラーが発生し、チャンクの挿入を再試行する必要があります。

あなたは私のSOの答えでベンチマーク用のコードとの完全な解決策を見つけることができHEREことができますことを願っています!


-1

ロックされたデータベースでエラーが発生する最も可能性の高い理由は、発行する必要があることです。

conn.commit()

データベース操作の終了後。そうしないと、データベースは書き込みロックされ、そのままになります。書き込みを待機している他のスレッドは、しばらくするとタイムアウトになります(デフォルトは5秒に設定されています。詳細については、http://docs.python.org/2/library/sqlite3.html#sqlite3.connectを参照してください)。 。

正しい同時挿入の例は次のとおりです。

import threading, sqlite3
class InsertionThread(threading.Thread):

    def __init__(self, number):
        super(InsertionThread, self).__init__()
        self.number = number

    def run(self):
        conn = sqlite3.connect('yourdb.db', timeout=5)
        conn.execute('CREATE TABLE IF NOT EXISTS threadcount (threadnum, count);')
        conn.commit()

        for i in range(1000):
            conn.execute("INSERT INTO threadcount VALUES (?, ?);", (self.number, i))
            conn.commit()

# create as many of these as you wish
# but be careful to set the timeout value appropriately: thread switching in
# python takes some time
for i in range(2):
    t = InsertionThread(i)
    t.start()

SQLiteが好きな場合、SQLiteデータベースで動作する他のツールがある場合、CSVファイルをSQLite dbファイルに置き換えたい場合、またはプラットフォーム間IPCのようなまれなことを行う必要がある場合、SQLiteは優れたツールであり、目的に非常に適しています。気分が悪い場合でも、別のソリューションを使用するようにプレッシャーをかけないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.