32

似乎有关并发访问的规则没有记录(在 Haskell 方面)并且只是假设开发人员熟悉正在使用的特定后端。对于生产需求,这是一个完全合理的假设,但对于临时原型设计和开发来说,如果 persistent-* 包更加独立,那就太好了。

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

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

编辑:一些示例代码现在如下。

在下面的代码中,我分叉了 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

注意对于本示例40TEST、 和所有记录的值是任意的。许多价值观,包括更现实的价值观,都会导致相同的行为。

forever另请注意,虽然当您在数据库事务(由 开始)中嵌套非终止操作(通过)时,它可能会明显被破坏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

值得注意的是,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(这似乎不太理想。)

于 2012-02-03T15:12:23.817 回答