有关并发访问持久数据库的规则是什么

Tho*_*son 32 haskell persistent yesod

似乎关于并发访问的规则是未记录的(在Haskell方面)并且简单地假设开发人员熟悉正在使用的特定后端.对于生产需求而言,这是一个完全合理的假设,但对于随意的原型设计和开发,如果持久性*包更加自包含,那将会很好.

那么,管理对persistent-sqlite和family的并发访问的规则是什么?隐含地,如果我们有连接池,则必须允许某种程度的并发,但是通常创建单个连接池并且调用replicateM x $ forkIO (useThePool connectionPool)会产生以下错误.

user error (SQLite3 returned ErrorBusy while attempting to perform step.)
Run Code Online (Sandbox Code Playgroud)

编辑:现在下面是一些示例代码.

在下面的代码中,我分叉了6个线程(任意数字 - 我的实际应用程序执行3个线程).每个线程不断存储和查找记录(来自其他线程正在访问的记录的唯一记录,但这无关紧要),打印其中一个字段.

{-# 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
Run Code Online (Sandbox Code Playgroud)

NB的值40,TEST和所有的记录是任意的这个例子.许多值,包括更现实的值,都会导致相同的行为.

另请注意,虽然forever在数据库事务(启动runSqlPool)中嵌套非终止操作(via )时可能会明显中断,但这不是核心问题.您可以反转这些操作并使事务任意小,但最终会导致定期异常.

输出通常如下:

$ ./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.)
Run Code Online (Sandbox Code Playgroud)

Tom*_*m B 16

值得注意的是,SQLite在许多系统上存储在类似NFS的卷(vboxsf,NFS,SMB,mvfs等)上时存在锁定问题,这导致SQLite甚至在您成功打开数据库之前就会出现该错误.这些卷可能会错误地实现fcntl()读/写锁.(http://www.sqlite.org/faq.html#q5)

假设这不是问题,还值得一提的是,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

编辑:persist-sqlite3库中的一些其他信息 This package includes a thin sqlite3 wrapper based on the direct-sqlite package, as well as the entire C library

"薄"包装让我决定看看它有多薄; 查看代码它看起来不像持久化包装器有任何防止对池的语句的保护失败,除了所需的后卫转换/发出错误和中断执行,虽然我必须提供警告,我不满意哈斯克尔.

您似乎必须防止池中的语句失败并重新尝试,或者您将初始化时的池大小限制为1(这似乎不太理想.)