4

私はQtの開発、スレッド(シグナルとスロット)とデータベース(およびSQLite)の処理方法については初めてです。上記のテクノロジーに取り組み始めてから 4 週間が経ちました。SOに質問を投稿するのはこれが初めてで、皆さんに来る前に調査を行ったと感じています。これは少し長く、おそらく重複しているように見えるかもしれませんが、重複またはtl;drとして却下する前に、一度よく読んでください.

コンテキスト:

データベースで特定の操作 X を実行する Windows アプリケーションに取り組んでいます。アプリケーションは Qt で開発され、データベース エンジンとして QSQLite を使用します。これはシングル スレッド アプリケーションです。つまり、テーブルは順番に処理されます。ただし、DB のサイズ (テーブルとレコードの数) が大きくなるにつれて、この処理は遅くなります。この操作 X の結果は、同じ DB 内の別の結果テーブルに書き込まれます。行われている処理は問題にとって重要ではありませんが、基本的には次のようになります。

Table_X_1 から行を
読み取ります。 Table_X_2 から行を読み取ります。 行に対して
いくつかの操作を行います (読み取りのみ)
結果を Table_X_Results テーブルにプッシュします (これは DB で実行される唯一の書き込みです)。

Table_X_1 と Table_X_2 は、列の数とタイプ、および行の数が同じですが、データのみが異なる場合があります。

私がやろうとしていること:

パフォーマンスを向上させるために、アプリケーションをマルチスレッド化しようとしています。最初に、2 つのスレッドを生成しています (QtConcurrentRun を使用)。2 つのテーブルは、たとえば A と B の 2 つのタイプに分類できます。各スレッドは、2 つのタイプのテーブルを処理します。スレッド内の処理は同じままです。つまり、各スレッド内でテーブルが順次処理されます。

この関数は、SELECT を使用して処理のために行をフェッチし、INSERT を使用して結果を結果テーブルに挿入するようなものです。結果を挿入するために、トランザクションを使用しています。

実際の操作を開始する前に、すべての中間テーブル、結果テーブル、およびインデックスを作成しています。私は毎回接続を開いたり閉じたりしています。スレッドについては、ループに入る前に接続を作成して開きます (スレッドごとに 1 つ)。

問題:

私の処理関数内で、次の(厄介で悪名高い、頑固な)エラーが発生します。

QSqlError(5, "行をフェッチできません", "データベースがロックされています")

(SELECT を使用して) DB から行を読み取ろうとすると、このエラーが発生します。これは、結果テーブルへの INSERT を実行しているのと同じ関数にあります。SELECT と INSERT は同じトランザクション (begin と commit のペア) にあります。INSERT には、準備済みステートメント (SQLiteStatement) を使用しています。

私がやっている一見奇妙なことの理由

  1. 簡単にできるので、QtConcurrentRun を使用してスレッドを作成しています。QThread を使用してみました (QThread をサブクラス化するのではなく、他の方法を使用します)。それも同じ問題につながります。
  2. アプリケーションのクラッシュを避けるために、DSQLITE_THREADSAFE=0 でコンパイルしています。デフォルト (DSQLITE_THREADSAFE=1) を使用すると、アプリケーションが SQLiteStatement::recordSet->Reset() でクラッシュします。また、デフォルトのオプションでは、信頼できない可能性がある内部 SQLITE 同期メカニズムが機能します。必要に応じて、明示的な同期を採用します。
  3. パフォーマンスを向上させるためにアプリケーションをマルチスレッド化し、これを行わない。そこで推奨されているすべての最適化を担当しています。
  4. QSQLITE_BUSY_TIMEOUT=0 で QSqlDatabase::setConnectOptions を使用します。リンクは、DBがすぐにロックされるのを防ぐため、スレッドが「平和的に死ぬ」のに適切な時間を与える可能性があることを示唆しています。これは失敗しました: DB は以前よりも頻繁にロックされました。

観察:

  1. データベースは、スレッドの 1 つが戻るとすぐにロックされます。この動作は一貫しています。
  2. DSQLITE_THREADSAFE=1 でコンパイルすると、スレッドの 1 つが戻ったときにアプリケーションがクラッシュします。呼び出しスタックは、私の関数では SQLiteStatement::recordSet->Reset() を指し、sqlite3.c では winMutexEnter() (EnterCriticalSection() から呼び出される) を指します。これも一貫しています。
  3. QtConcurrentRun を使用して作成されたスレッドは、すぐには終了しません。
  4. QThreads を使用すると、それらを返すことができません。つまり、シグナルとスロットを正しく接続したのに、スレッドが戻らない気がします。スレッドを待つ正しい方法と、スレッドが死ぬまでにかかる時間は?
  5. 実行を終了したスレッドは返されません。DB がロックされているため、エラーが発生します。
  6. SQLITE_BUSY を確認し、スレッドをスリープさせようとしましたが、機能しませんでした。Qtでスリープする正しい方法は何ですか(QtConcurrentRunまたはQThreadsで作成されたスレッドの場合)?
  7. 接続を閉じると、次の警告が表示されます。

    QSqlDatabasePrivate::removeDatabase: 接続 'DB_CONN_CREATE_RESULTS' はまだ使用中です。すべてのクエリが機能しなくなります。

    これは何か意味がありますか?いくつかのリンクは、ローカル QSqlDatabase を使用しているためにこの警告が発生し、接続がクラス メンバーになっている場合は発生しないことを示唆しています。しかし、それが私の問題の原因でしょうか?

さらなる実験:

  1. 結果テーブル (Table_X_Results) のみを含む別のデータベースを作成することを考えています。理論的根拠は、スレッドが 1 つの DB (私が現在持っているもの) から読み取る一方で、別の DB に書き込むことになるということです。しかし、私はまだ同じ問題に直面するかもしれません。さらに、フォーラムとウィキで、2つのスレッドが同じ DB で読み取りと書き込みを行う可能性があることを読みました。では、なぜこのシナリオを機能させることができないのでしょうか?
  2. 現在、SQLITE バージョン 3.6.17 を使用しています。それが問題でしょうか?バージョン 3.8.5 を使用した場合、状況は改善されますか?

すでに調べた Web リソースを投稿しようとしましたが、「2 つ以上のリンクを投稿するには 10 人の担当者が必要です」というメッセージが表示されます。どんな助け/提案も大歓迎です。

4

0 に答える 0