none
ODBCのマルチスレッド性 RRS feed

  • 質問

  • お世話になります。ODBCに関する質問です。データベース関連のコーディングは初めてですので、何か前提がおかしい等ございましたらご指摘ください。

    環境:Win7 with SP1, VS2012 update4, C++ native app, ODBC API利用(非MFC)

    SQLite3を、ODBC Wrapper(http://www.ch-werner.de/sqliteodbc/)を経由して利用しようとしています。

    SQLiteにはスレッドセーフに関して3つのモード(http://www.sqlite.org/threadsafe.html)があるのですが、上記のSQLite ODBC Wrapperは「制限なし(どんな操作を並行に行っても安全)」になるようSQLiteを利用しています(ソースコードとmakefileを確認しました)。

    そうすると、全体としてのスレッドセーフ性は

    • Wrapper(ドライバ内部)の部分のスレッドセーフ性
    • ODBC API→ドライバ の間のスレッドセーフ性

    の2点が関わってくると思うのですが、そのうちの後者「ODBC API→ドライバ」についてのスレッドセーフ性についてご存知の方はいらっしゃいませんか。

    チームメンバーからは

    • 1つの接続(セッション)においては複数のスレッドで同時にODBC API呼び出しをしてはならないが、複数の接続を行えば、個々の接続でAPI呼び出しを並行して行える。

    という話を聞いたのですが、それが記述されているMSDNライブラリ等の情報源が見つかりません。

    アプリケーション側でやりたいことは、以下のような感じです。

    • 書き込みと読み込みを並行して行いたい
    • 個々の書き込みは相互排他する
    • 個々の読み込みは並行して行いたい

    以上、どうぞよろしくお願い致します。

    2014年7月2日 2:51

回答

  • Multithreading
    http://msdn.microsoft.com/en-us/library/ms715361(v=vs.85).aspx

    というのもありました。

    ざっと見ただけですが、ドライバがスレッドセーフというのは、単一ハンドルを複数スレッドから呼び出した場合のスレッドセーフ性について書いてあるように見えます。

    つまり、ハンドルが別なら通常はスレッドセーフで使える、単一ハンドルをスレッド間で使いまわすならドライバの実装依存、と読めます。

    ※ハンドル別の場合でもドライバ依存といえばドライバ依存だと思いますが、ドライバへの要求仕様で複数ハンドルならスレッドセーフであることは前提条件となっているのかもしれません(まあ一般に考えれば、そうでなくては困りますので)

    2014年7月2日 12:30
  • データベース関連のコーディングは初めてとのことで、別の観点で説明をします。

    データベースでは単に排他制御すれば問題が解決するというわけではありません。この問題は同時実行制御(並行性制御)と呼ばれ、同時実行制御の種類に簡単に説明がありますが主なアプローチとして2種類あります。質問者さんはペシミスティック(悲観的)、オプディミスティック(楽観的)のどちらを採用するかを決定する必要があります。

    どちらの同時実行制御を採用したとしても、最終的なデータベースエンジン(今回ではSQLite)がマルチスレッドに対応していればよく、途中のレイヤーはあまり関係ありません。

    2014年7月2日 5:40

すべての返信

  • フォーラム オペレーターの星 睦美です。
    Takahiro Toyama さん、投稿ありがとうございます。

    SQLiteはマイクロソフトの製品、技術ではなくパブリック ドメイン ソフトウェアであり、こちらのVisual C++ に関するフォーラムがTakahiro Toyama さんの質問に役立つかどうか分かりませんので返信させていただきました。

    私のほうで日本語「SQLite + スレッドセーフ」で検索してみたところ、いくつかユーザーのブログがありました。
    また、SQLite に関するサイトがありますのでご紹介します。(こちらは英語のページです。)

    ・SQLite
    http://www.sqlite.org/index.html


    フォーラム オペレーター 星 睦美 - MSDN Community Support

    2014年7月2日 5:03
  • 星様

    上記に書いたとおり、本スレッドはSQLite固有の問題について話していないつもりです。

    内部的な動作としては、私の想像としては

    1. アプリケーションがODBC APIを呼び出す
    2. WindowsのODBCの機構が、ODBC Driverを呼び出す
    3. ODBC Driverが要求を処理する

    という流れになっていると考えています。

    このうち、SQLiteおよびSQLite ODBC Driverが担当するのは上記(3)で、私が質問しているのは(2)の部分についてのスレッドセーフ性です。

    ODBC Driverがなんであろうと、Driver側でのスレッドセーフ性が担保されているならば、あとはWindowsが提供するODBCの機構の部分の問題であると思い、先の質問を投げた次第です。


    2014年7月2日 5:10
  • データベース関連のコーディングは初めてとのことで、別の観点で説明をします。

    データベースでは単に排他制御すれば問題が解決するというわけではありません。この問題は同時実行制御(並行性制御)と呼ばれ、同時実行制御の種類に簡単に説明がありますが主なアプローチとして2種類あります。質問者さんはペシミスティック(悲観的)、オプディミスティック(楽観的)のどちらを採用するかを決定する必要があります。

    どちらの同時実行制御を採用したとしても、最終的なデータベースエンジン(今回ではSQLite)がマルチスレッドに対応していればよく、途中のレイヤーはあまり関係ありません。

    2014年7月2日 5:40
  • 佐祐理様

    お返事ありがとうございます。ペシミスティック、オプディミスティックについては用語自体知りませんでした。ありがとうございます。ググって以下のページを見つけたのですが、さっと読んだ感じではあまり理解できませんでした。もう少し読み込みします。

    http://kurokawh.blogspot.jp/2013/11/sqlite-sqlite.html

    http://publib.boulder.ibm.com/infocenter/soliddb/v6r3/index.jsp?topic=/com.ibm.swg.im.soliddb.sql.doc/doc/pessimistic.vs.optimistic.concurrency.control.html

    2014年7月2日 6:27
  • 「ODBC API→ドライバ の間のスレッドセーフ性」というのは、一つのアプリケーションの問題ではないですよね? Windows上で複数のアプリケーションが動作していて、もしこのスレッドセーフが保たれていないのであれば、Windowsの機構として破綻する可能性を常にはらんでいるという事になります。
    また、佐祐理さんが書かれていることに加え、読み書き同時に行うということですので、デッドロックに注意されると良いと思います。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2014年7月2日 6:32
  • 「ODBC API→ドライバ の間のスレッドセーフ性」というのは、一つのアプリケーションの問題ではないですよね? Windows上で複数のアプリケーションが動作していて、もしこのスレッドセーフが保たれていないのであれば、Windowsの機構として破綻する可能性を常にはらんでいるという事になります。

    内部の作りはわからないので、あくまで想定ですが・・・。

    ODBC APIがあるプロセスで呼び出された時、ODBCの機構がそのプロセス空間に静的領域やヒープを確保して処理を行っており、その領域を複数のステートメントにまたがって利用したりしていれば、プロセス間では安全ですが同一プロセス内ではスレッドセーフではない可能性があると思いますが、いかがでしょうか。

    そう考えると、問題は各プロセスで閉じて発生することになるので、ODBCを使ってアプリを組んでいる皆様は、自身のアプリを組む際にこの点を考慮して自前でスレッドセーフに作っているのではないか?と思った次第です。


    2014年7月2日 6:40
  • ODBC APIがあるプロセスで呼び出された時、ODBCの機構がそのプロセス空間に静的領域やヒープを確保して処理を行っており、その領域を複数のステートメントにまたがって利用したりしていれば、プロセス間では安全ですが同一プロセス内ではスレッドセーフではない可能性があると思いますが、いかがでしょうか。

    ODBCはdllでしたね。すみません、早合点していました。どうも、接続となると共通で使うというのが頭にあったものですから・・・(言い訳)

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2014年7月2日 7:32
  • 下記はMFCでの話であり、直接的な情報ではないのですが、

    ODBC クラスおよびスレッド
    http://msdn.microsoft.com/ja-jp/library/xx6t3y0x(v=vs.80).aspx

    ----
    ODBC クラスは、ODBC API をラップしているため、ビルド元のコンポーネントのマルチスレッド サポートに依存します。たとえば、多くの ODBC ドライバはスレッド セーフではありません。したがって、このようなスレッド セーフでないドライバで使用された MFC ODBC クラスはスレッド セーフでありません。ドライバがスレッド セーフかどうかを確認して使用してください。
    ----

    との記述から見ても、スレッドセーフかどうかはドライバの実装に依存する、と読めます。

    というか、そもそもそうでないとODBCがサーバ環境などでは使い物になりませんので、スレッドセーフにできないということは考えられません。

    あとはマルチスレッド対応版かそうでないかでDLLの使い分けがあるか、というところになると思います(シングルスレッド版ライブラリとマルチスレッド版ライブラリがあるようなタイプ)。
    そういうのがないのであれば、スレッドセーフであると考えて問題ないはずです。

    2014年7月2日 8:37
  • 軽く調べても言及が見つからないので「スレッドセーフではない」のではないでしょうか?

    内部的な動作としては、私の想像としては
    1. アプリケーションがODBC APIを呼び出す
    2. WindowsのODBCの機構が、ODBC Driverを呼び出す
    3. ODBC Driverが要求を処理する

    詳しく調べた事もないですが、そういった動作と思われる低レベルラッパーの ODBC 関連 API はスレッドセーフではないと考えてました・・・。

    自分の返信でないですが、「なちゃ様の情報」からは「ODBC API 自体はスレッドセーフではなく」、ドライバがスレッドセーフかどうかに依存すると考えられますね。

    コネクションやトランザクションのセッション管理はドライバレベルで行っておかしくないので、そこで対応してる場合があってもおかしくないでしょうけど、普通ならわざわざ対応しないのじゃないですかね?

    SQLite がよくわかってませんが、今回の場合 1. または 0. の部分で ODBC ラッパーが存在している予定なのですよね? そこがスレッドセーフを担保するのが一般的で自然な気がします。

    蛇足ですが、
    チームメンバーからは
    • 1つの接続(セッション)においては複数のスレッドで同時にODBC API呼び出しをしてはならないが、複数の接続を行えば、個々の接続でAPI呼び出しを並行して行える。

    という話を聞いたのですが、それが記述されているMSDNライブラリ等の情報源が見つかりません。

    チームメンバーの言い分が正しくて、そうするのが普通と思います・・・根拠も情報源も示せないですが賛成票を投じたい。

    2014年7月2日 10:10
  • オプティミスティック同時実行制御であればロックしないので(ほとんど)デッドロックは起こらないかと…。

    質問者さんへ:
    ところで安易にスレッドセーフを議論されていますが、データベースとの論理的なコネクション、1つのコネクションを複数のスレッドから同時に操作するスレッドセーフと、各々のスレッドがそれぞれコネクションを持ち、そのコネクションの範囲内で操作をする際のスレッドセーフと、2種類あります。
    一般論としては前者も含んでしまいますが、これは開発者が避ければいいことで、後者に絞るべき、でないと話が発散します。

    # 余談ですが、SQL Serverの場合、MARS; Multiple Active Result Setと言い1つのコネクションで複数のクエリを実行できます。

    更に脱線すると、そもそもネイティブアプリにおいて、マルチスレッドからDBにアクセスすることなんてそうそうあるんだろうか、と。アプリ自体はマルチスレッド動作するにしてもDBアクセスは1ヶ所にまとめたりしないのでしょうか…?

    2014年7月2日 11:53
  • Multithreading
    http://msdn.microsoft.com/en-us/library/ms715361(v=vs.85).aspx

    というのもありました。

    ざっと見ただけですが、ドライバがスレッドセーフというのは、単一ハンドルを複数スレッドから呼び出した場合のスレッドセーフ性について書いてあるように見えます。

    つまり、ハンドルが別なら通常はスレッドセーフで使える、単一ハンドルをスレッド間で使いまわすならドライバの実装依存、と読めます。

    ※ハンドル別の場合でもドライバ依存といえばドライバ依存だと思いますが、ドライバへの要求仕様で複数ハンドルならスレッドセーフであることは前提条件となっているのかもしれません(まあ一般に考えれば、そうでなくては困りますので)

    2014年7月2日 12:30
  • う~ん、なちゃ様の引用付近を検索してたのに見つけれなかった・・・。

    そこの内容からすると、ODBC ドライバはスレッドセーフである事が期待されてる感じに思えます。

    しかし MFC での言及からするとドライバ依存っぽいので ODBC ドライバ仕様で必須という訳ではないといった所なのかな・・・。

    例示の「SQL実行のキャンセル」は、同一ハンドル(コネクション)を別スレッドで扱う代表例ですね・・・これ以外の利用例知らないけど。

    質問者様は「ODBC API はスレッドセーフなのか?」と質問してると考えてましたが、やりたい内容を読むと佐祐理様がいっているように DB の同時実行制御の方が気になりますけど、そちらだと SQLite の話になるでしょうから、そちらのコミュニティで聞く方が良い返事が期待できる気がします。

    > 佐祐理様

    更に脱線すると、そもそもネイティブアプリにおいて、マルチスレッドからDBにアクセスすることなんてそうそうあるんだろうか、と。アプリ自体はマルチスレッド動作するにしてもDBアクセスは1ヶ所にまとめたりしないのでしょうか…?

    たしかに滅多にする事ないでしょうけど、何故か自分は数回やってたもので・・・・。


    • 編集済み kyano30 2014年7月2日 17:40 API が抜けてた
    2014年7月2日 17:38
  • 皆様、情報とアドバイスをありがとうございます。

    なちゃ様が紹介して下さったページの冒頭をgoogle翻訳で翻訳した結果を以下に示しておきます:

    マルチスレッドのオペレーティングシステムでは、ドライバはスレッドセーフでなければなりません。すなわち、アプリケーションは、複数のスレッドで同じハンドルを使用することが可能でなければならない。どのようにこれが達成されるドライバ固有であり、ドライバーは同時に2つの異なるスレッドで同じハンドルを使用する試みをシリアル化する可能性が高い。

    ハンドルがステートメントハンドルのことであれば、(ドライバさえスレッドセーフに作ってあれば)どんな操作をしてもスレッドセーフである、というふうに読めますね。

    >佐祐理様

    データベースとの論理的なコネクション、1つのコネクションを複数のスレッドから同時に操作するスレッドセーフと、各々のスレッドがそれぞれコネクションを持ち、そのコネクションの範囲内で操作をする際のスレッドセーフと、2種類あります。一般論としては前者も含んでしまいますが、これは開発者が避ければいいことで、後者に絞るべき、でないと話が発散します。

    最初に質問した際は前者も含めて考えておりました。というのは、アプリケーションを組む上でどのレベルで処理をシリアル化したらよいのかが不明だったためです。佐祐理様の他の書き込みを拝見するに、DBアプリケーションでは並列性制御を考えるとスレッドの構成の仕方に制約を持たせるような設計が一般的なナレッジとして確立しているようですので、そこをもっと学習していきたいと思いました。マルチスレッドからDBにアクセスする要件ですが、既存の設計がマイクロサービスの個々の要求をスレッドプールで処理するようなもので、そこに上乗せする形で追加開発しているために発生している感じです。

    2014年7月2日 23:47
  • 最初に質問した際は前者も含めて考えておりました。というのは、アプリケーションを組む上でどのレベルで処理をシリアル化したらよいのかが不明だったためです。佐祐理様の他の書き込みを拝見するに、DBアプリケーションでは並列性制御を考えるとスレッドの構成の仕方に制約を持たせるような設計が一般的なナレッジとして確立しているようですので、そこをもっと学習していきたいと思いました。マルチスレッドからDBにアクセスする要件ですが、既存の設計がマイクロサービスの個々の要求をスレッドプールで処理するようなもので、そこに上乗せする形で追加開発しているために発生している感じです。

    コネクションの確立と破棄には確かにコストがかかります。ですので1つのコネクションをスレッド間で共用したいと考えられたかもしれませんが、スレッドプールと同様にODBCのコネクションにもコネクションプールが存在します。

    軽くググった限りではSQLiteおよびODBCはコネクションプールをサポートしているようなので、あとはODBC Wrapperがサポートしているかどうかを確認し、使えるようであれば、適切な範囲でコネクションのOpen / Closeを行い、実際のコネクションについてはODBCに任せることをお勧めします。

    …使えないようであればODBC Wrapperの使用から再検討した方がいいかもしれません。ネイティブアプリでなぜSQLiteを直接操作しないのか少々疑問です。

    2014年7月3日 1:45
  • オプティミスティック同時実行制御であればロックしないので(ほとんど)デッドロックは起こらないかと…。

    TableAdapterなど.NETが標準で生成するオプティミスティック同時実行制御であれば、佐祐理さんが言われているようにデッドロックは発生しないはずなので、佐祐理さんはそれを言われているのかもしれませんが(ほとんどとも書かれていますし)、正確には、オプティミスティック同時実行制御とデッドロックは直接関係ないということを念のために補足しておきます。
    オプティミスティック同時実行制御はアプリケーションレベルの話であり、デッドロックはデータベースレベルの話です。よって、オプティミスティック同時実行制御が、最終的にどのようにデータベースに書き込みに行くかによって、デッドロックが発生する可能性があります。自分でストアドプロシージャを組んで書き込みを行う場合など注意が必要です。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2014年7月3日 2:23