2012-01-29 43 views
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 

NB的所有記錄中的40TEST值,並且是任意的這個例子。許多價值觀,包括更現實的價值觀,都會導致相同的行爲。

另請注意,雖然在嵌套數據庫事務(由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'的更多細節嗎? – 2012-01-29 23:03:23

+0

@丹伯頓我已經通過編輯提供了更多信息。現在使用示例代碼,對於那些對持久性有所瞭解的人可能是盲目的錯誤。 – 2012-01-30 01:31:58

+0

@ ThomasM.DuBuisson,您是否嘗試過使用select而不是insert,並查看是否可以重現該錯誤?如果在select上沒有發生該錯誤,那麼該錯誤可能是某處引發的死鎖預防異常,尤其是在您嘗試執行併發插入時。沒有意義,但如果你有一個線程池。 – Sal 2012-01-30 01:52:27

回答

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

編輯: 從堅持-sqlite3的庫中的一些附加信息 This package includes a thin sqlite3 wrapper based on the direct-sqlite package, as well as the entire C library

「薄」的包裝讓我決定看看它,看看它是多麼的薄;看看代碼,它看起來好像持久包裝器有任何防範池失敗的聲明,除了需要的守衛來翻譯/發出錯誤和中斷執行,但我必須提供的警告,我不舒服哈斯克爾。

看起來,您將不得不防止池中的語句失敗並重新嘗試,或者將初始化時的池大小限制爲1(這似乎不太理想。)