2012-01-29 6 views
32

並行アクセスに関するルールは(Haskell側で)文書化されておらず、開発者が使用されている特定のバックエンドに精通していると仮定します。本番用の場合、これは完全に正当な仮定ですが、カジュアルなプロトタイピングと開発のためには、persistent- *パッケージがもう少し自己完結型であればいいでしょう。永続データベースへの同時アクセスに関する規則

したがって、永続的なsqliteおよびファミリへの同時アクセスを制御するルールは何ですか?暗黙のうちに、接続のプールを持っているのにある程度の並列性が許されている必要がありますが、単一の接続プールを作成してreplicateM x $ forkIO (useThePool connectionPool)を呼び出すと、以下のエラーが発生します。

user error (SQLite3 returned ErrorBusy while attempting to perform step.) 

編集:いくつかのコード例を以下に示します。

以下のコードでは、6つのスレッド(任意の数 - 私の実際のアプリケーションは3つのスレッドを行います)をforkします。各スレッドは常にレコードを格納して検索します(他のスレッドからアクセスされたレコードからの一意のレコードですが、それは問題ではありません)。フィールドの1つを印刷します。

{-# LANGUAGE TemplateHaskell, QuasiQuotes 
      , TypeFamilies, FlexibleContexts, GADTs 
      , OverloadedStrings #-} 
import Control.Concurrent (forkIO, threadDelay) 
import Database.Persist 
import Database.Persist.Sqlite hiding (get) 
import Database.Persist.TH 
import Control.Monad 
import Control.Monad.IO.Class 

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist| 
SomeData 
    myId Int 
    myData Double 
    MyId myId 
|] 

main = withSqlitePool "TEST" 40 $ \pool -> do 
    runSqlPool (runMigration migrateAll) pool 
    mapM_ forkIO [runSqlPool (dbThread i) pool | i <- [0..5]] 
    threadDelay maxBound 

dbThread :: Int -> SqlPersist IO() 
dbThread i = forever $ do 
    x <- getBy (MyId i) 
    insert (SomeData i (fromIntegral i)) 
    liftIO (print x) 
    liftIO (threadDelay 100000) -- Just to calm down the CPU, 
           -- not needed for demonstrating 
           -- the problem 

NBは、40の値、TEST、およびすべてのレコードは、この例のために任意です。より現実的な値を含む多くの値は、同じ動作を引き起こします。

DBトランザクション(runSqlPoolで開始)の中で非終了アクション(forever経由)をネストすると明らかに壊れている可能性がありますが、これはコアの問題ではありません。これらの操作を逆転させ、トランザクションを任意に小さくすることはできますが、定期的な例外を除いても終了します。

出力が通常のようである:注目に値する

$ ./so 
Nothing 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorConstraint while attempting to perform step.) 
+0

あなたは 'useThePool'についてもっと詳しく説明できますか? –

+0

@ダンブルトン私は編集によってさらに情報を与えました。さて、永続的なものについて何か知っている人にはおそらく盲目的に間違っているサンプルコードがあります。 –

+0

@ ThomasM.DuBuisson、あなたはinsertの代わりにselectを使ってみましたが、エラーを再現できるかどうか確認してください。 selectでエラーが発生しない場合は、特に同時挿入を試みている場合は、どこかにスローされたデッドロック防止例外が原因の可能性があります。あなたがスレッドプールを持っている場合でも意味をなさない。 – Sal

答えて

16

何かがSQLiteのはどの多くのシステムでNFSのようなボリューム(等vboxsf、NFS、SMB、MVFS)に格納されている場合、ロックの問題を有することですデータベースを正常に開く前にSQLiteにそのエラーを通知させます。これらのボリュームは、fcntl()読み取り/書き込みロックを誤って実装することがあります。 (http://www.sqlite.org/faq.html#q5

それは問題ではありませんと仮定すると、それはそれは2件の書き込みが同じで発生していないことを確認するために、ファイル・システム・ロックを使用してSQLiteのは本当にネイティブ同時「接続」(http://www.sqlite.org/faq.html#q6)をサポートしていないことも言及する価値があります時間。 (http://www.sqlite.org/lockingv3.htmlの3.0を参照してください)

これがすべてわかっていると仮定すると、さまざまな種類のロックが取得された方法のいくつかが変更されたため、環境で使用できるsqlite3のバージョンを確認することもできます3.xのシリーズ:http://www.sqlite.org/sharedcache.html

編集: 持続-sqlite3のライブラリーからのいくつかの追加情報 This package includes a thin sqlite3 wrapper based on the direct-sqlite package, as well as the entire C library

「シン」ラッパーが、私はそれがどれだけ薄く見て、それを見てみることにした作られました。コードを見れば、永続ラッパーがプールへのステートメントに対してガードを持っているかのようには見えませんが、エラーを翻訳して実行するために必要なガード以外はガードしますが、私は快適ではないハスケル。

プールのステートメントに対して失敗したり再試行したり、初期化時にプールサイズを1に制限したりする必要があります(これは理想的ではないようです)。)

関連する問題