Каковы правила одновременного доступа к постоянной базе данных - PullRequest
32 голосов
/ 29 января 2012

Кажется, что правила для одновременного доступа недокументированы (на стороне 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. Значения 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.)

1 Ответ

16 голосов
/ 03 февраля 2012

Стоит отметить, что 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: 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

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

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

...