Подтвердить что ты не робот

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

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

Итак, каковы правила, регулирующие одновременный доступ к persistent-sqlite и семье? Неявно, допускается некоторая степень concurrency, если у нас есть пулы соединений, но тривиальное создание единственного пула соединений и вызов 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), это не является основной проблемой. Вы можете инвертировать эти операции и сделать транзакции произвольно маленькими, но все же в конечном итоге иметь периодические исключения.

Вывод обычно похож:

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

Ответ 1

Что-то стоит отметить, что SQLite имеет проблемы с блокировкой при хранении на NFS-подобных томах (vboxsf, NFS, SMB, mvfs и т.д.) во многих системах, которые заставляют SQLite давать эту ошибку еще до того, как вы успешно открыли база данных. Эти тома могут неправильно реализовать блокировки чтения/записи fcntl(). (http://www.sqlite.org/faq.html#q5)

Предполагая, что не проблема, стоит также упомянуть, что SQLite на самом деле не поддерживает параллельные "соединения" (http://www.sqlite.org/faq.html#q6), поскольку он использует блокировки файловой системы чтобы две записи не возникали одновременно. (См. Раздел 3.0 http://www.sqlite.org/lockingv3.html)

Предполагая, что все это известно, вы также можете проверить, какая версия sqlite3 у вас есть в вашей среде, так как некоторые изменения в способе получения различных видов блокировок произошли в серии 3.x: <а3 >

Изменить: Некоторая дополнительная информация из библиотеки persist-sqlite3 This package includes a thin sqlite3 wrapper based on the direct-sqlite package, as well as the entire C library

"Тонкая" обертка заставила меня решиться взглянуть на нее, чтобы увидеть, насколько она тонкая; глядя на код, он выглядит не так, как если бы постоянная оболочка имела какие-либо защитные меры против инструкции в пул, за исключением требуемого защитника, чтобы перевести/испустить ошибку и выполнить прерывание, хотя я должен предоставить предостережение, которое мне неудобно Haskell.

Похоже, вам придется защищать от утверждения в пуле неудачу и повторную попытку или ограничить размер пула при инициализации 1 (что кажется менее идеальным).