32

同時アクセスに関するルールは (Haskell 側では) 文書化されておらず、開発者が使用されている特定のバックエンドに精通していると単純に想定しているようです。本番環境のニーズでは、これは完全に正当な仮定ですが、カジュアルなプロトタイピングと開発では、persistent-* パッケージがもう少し自己完結型であるとよいでしょう。

では、persistent-sqlite とファミリーへの同時アクセスを管理するルールは何ですか? 暗黙的に、接続のプールがある場合、ある程度の同時実行が許可されている必要がありますが、単純に単一の接続プールを作成して呼び出すとreplicateM x $ forkIO (useThePool connectionPool)、次のエラーが発生します。

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

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

以下のコードでは、6 つのスレッドを分岐しています (任意の数 - 私の実際のアプリケーションは 3 つのスレッドを実行します)。各スレッドは常にレコード (他のスレッドによってアクセスされているレコードからの一意のレコードですが、それは問題ではありません) を格納して検索し、フィールドの 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

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

foreverまた、 ( によって開始された) DB トランザクション内に非終了アクションを ( 経由で) ネストすると、明らかに壊れる可能性がありますがrunSqlPool、これは主要な問題ではないことに注意してください。これらの操作を逆にして、トランザクションを任意に小さくすることができますが、それでも定期的な例外が発生します。

通常、出力は次のようになります。

$ ./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.)
4

1 に答える 1

16

注目に値するのは、多くのシステムで NFS のようなボリューム (vboxsf、NFS、SMB、mvfs など) に格納されている場合、SQLite にはロックに関する問題があり、データベースを正常に開く前でも SQLite でエラーが発生することです。これらのボリュームは、fcntl() 読み取り/書き込みロックを正しく実装していない可能性があります。( http://www.sqlite.org/faq.html#q5 )

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

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

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

「薄い」ラッパーを見て、どれだけ薄いかを確認することにしました。コードを見ると、エラーを変換/発行して実行を中断するために必要なガードを除いて、永続的なラッパーがプールへのステートメントに対するガードを持っているようには見えませんが、私は慣れていないという警告を提供する必要がありますハスケル。

プール内のステートメントが失敗して再試行するのを防ぐか、初期化時にプール サイズを 1 に制限する必要があるようです (これは理想的とは言えません)。

于 2012-02-03T15:12:23.817 に答える