Каковы правила одновременного доступа к постоянной базе данных

Похоже, что правила о параллельном доступе недокументированы (со стороны Haskell) и просто предполагают, что разработчик знаком с конкретным используемым бэкендом. Для производственных нужд это вполне оправданное предположение, но для случайного прототипирования и разработки было бы неплохо, если бы пакеты persistent-* были немного более самостоятельными.

Итак, каковы правила, регулирующие одновременный доступ к persistent-sqlite и его родственникам? Неявно, должна быть какая-то степень параллелизма, если у нас есть пулы соединений, но тривиальное создание одного пула соединений и вызов replicateM x $ forkIO (useThePool connectionPool) дает следующую ошибку.

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

EDIT: Некоторые примеры кода приведены ниже.

В приведенном ниже коде я форкнул 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 Значения полей 40, TEST и всех записей в данном примере произвольны. Многие значения, включая более реалистичные, приводят к такому же поведению.

Также обратите внимание, что, хотя это может быть очевидно нарушено, если вы вложите не завершающееся действие (через 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.)
32
задан Thomas M. DuBuisson 2 February 2012 в 22:33
поделиться