10

私はHaskellを学ぶJavaプログラマーです。
私はHappstackを使用し、HDBCを介してデータベースと通信する小さなWebアプリで作業しています。

select関数とexec関数を作成し、次のように使用します。

module Main where

import Control.Exception (throw)

import Database.HDBC
import Database.HDBC.Sqlite3 -- just for this example, I use MySQL in production

main = do
    exec "CREATE TABLE IF NOT EXISTS users (name VARCHAR(80) NOT NULL)" []

    exec "INSERT INTO users VALUES ('John')" []
    exec "INSERT INTO users VALUES ('Rick')" []

    rows <- select "SELECT name FROM users" []

    let toS x = (fromSql x)::String
    let names = map (toS . head) rows

    print names

ご覧のとおり、非常にシンプルです。クエリパラメータ結果があります。
接続の作成とコミット/ロールバックはselectとexecの中に隠されています。
これは良いことです。「ロジック」コードでは気にしたくありません。

exec :: String -> [SqlValue] -> IO Integer
exec query params = withDb $ \c -> run c query params

select :: String -> [SqlValue] -> IO [[SqlValue]]
select query params = withDb $ \c -> quickQuery' c query params

withDb :: (Connection -> IO a) -> IO a
withDb f = do
    conn <- handleSqlError $ connectSqlite3 "users.db"
    catchSql
        (do r <- f conn
            commit conn
            disconnect conn
            return r)
        (\e@(SqlError _ _ m) -> do
            rollback conn
            disconnect conn
            throw e)

悪い点:

  • 呼び出しごとに常に新しい接続が作成されます-これにより、高負荷でのパフォーマンスが低下します
  • DBURL「users.db」はハードコードされています-編集せずに他のプロジェクトでこれらの関数を再利用することはできません

質問1:定義された(最小、最大)数の同時接続で接続のプールを導入して、接続がselect / exec呼び出し間で再利用されるようにするにはどうすればよいですか?

質問2:「users.db」文字列を構成可能にする方法は?(クライアントコードに移動する方法は?)

これは透過的な機能である必要があります。ユーザーコードは、明示的な接続処理/解放を必要としないはずです。

4

3 に答える 3

21

resource-poolパッケージは、データベース接続プーリングに使用できる高性能のリソースプールを提供します。例えば:

import Data.Pool (createPool, withResource)

main = do
    pool <- createPool newConn delConn 1 10 5
    withResource pool $ \conn -> doSomething conn

1つのサブプールと最大5つの接続を持つデータベース接続プールを作成します。各接続は、破棄される前に10秒間アイドル状態になります。

于 2012-05-01T17:50:40.867 に答える
10

質問2:私はHDBCを使用したことがありませんが、おそらくこのようなものを書くでしょう。

trySql :: Connection -> (Connection -> IO a) -> IO a
trySql conn f = handleSql catcher $ do
    r <- f conn
    commit conn
    return r
  where catcher e = rollback conn >> throw e

関数の外側のどこかを開き、Connection関数内で切断しないでください。

質問1:うーん、接続プールの実装はそれほど難しくないようです...

import Control.Concurrent
import Control.Exception

data Pool a =
    Pool { poolMin :: Int, poolMax :: Int, poolUsed :: Int, poolFree :: [a] }

newConnPool low high newConn delConn = do
    cs <- handleSqlError . sequence . replicate low newConn
    mPool <- newMVar $ Pool low high 0 cs
    return (mPool, newConn, delConn)

delConnPool (mPool, newConn, delConn) = do
    pool <- takeMVar mPool
    if length (poolFree pool) /= poolUsed pool
      then putMVar mPool pool >> fail "pool in use"
      else mapM_ delConn $ poolFree pool

takeConn (mPool, newConn, delConn) = modifyMVar mPool $ \pool ->
    case poolFree pool of
        conn:cs ->
            return (pool { poolUsed = poolUsed pool + 1, poolFree = cs }, conn)
        _ | poolUsed pool < poolMax pool -> do
            conn <- handleSqlError newConn
            return (pool { poolUsed = poolUsed pool + 1 }, conn)
        _ -> fail "pool is exhausted"

putConn (mPool, newConn, delConn) conn = modifyMVar_ mPool $ \pool ->
    let used = poolUsed pool in
    if used > poolMin conn
      then handleSqlError (delConn conn) >> return (pool { poolUsed = used - 1 })
      else return $ pool { poolUsed = used - 1, poolFree = conn : poolFree pool }

withConn connPool = bracket (takeConn connPool) (putConn conPool)

私はそれをコンパイルテストさえしていないので(そしてfailかなり不親切です)、おそらくこれを逐語的にとるべきではありませんが、アイデアは次のようなことをすることです

connPool <- newConnPool 0 50 (connectSqlite3 "user.db") disconnect

必要に応じて回しconnPoolます。

于 2009-07-17T14:59:42.483 に答える
1

上記のコードを変更しましたが、少なくともコンパイルできるようになりました。

module ConnPool ( newConnPool, withConn, delConnPool ) where

import Control.Concurrent
import Control.Exception
import Control.Monad (replicateM)
import Database.HDBC

data Pool a =
    Pool { poolMin :: Int, poolMax :: Int, poolUsed :: Int, poolFree :: [a] }

newConnPool :: Int -> Int -> IO a -> (a -> IO ()) -> IO (MVar (Pool a), IO a, (a -> IO ()))
newConnPool low high newConn delConn = do
--    cs <- handleSqlError . sequence . replicate low newConn
    cs <- replicateM low newConn 
    mPool <- newMVar $ Pool low high 0 cs 
    return (mPool, newConn, delConn)

delConnPool (mPool, newConn, delConn) = do
    pool <- takeMVar mPool
    if length (poolFree pool) /= poolUsed pool
      then putMVar mPool pool >> fail "pool in use"
      else mapM_ delConn $ poolFree pool

takeConn (mPool, newConn, delConn) = modifyMVar mPool $ \pool ->
    case poolFree pool of
        conn:cs ->
            return (pool { poolUsed = poolUsed pool + 1, poolFree = cs }, conn)
        _ | poolUsed pool < poolMax pool -> do
            conn <- handleSqlError newConn
            return (pool { poolUsed = poolUsed pool + 1 }, conn)
        _ -> fail "pool is exhausted"

putConn :: (MVar (Pool a), IO a, (a -> IO b)) -> a -> IO ()
putConn (mPool, newConn, delConn) conn = modifyMVar_ mPool $ \pool ->
    let used = poolUsed pool in
    if used > poolMin pool
    then handleSqlError (delConn conn) >> return (pool { poolUsed = used - 1 })
    else return $ pool { poolUsed = used - 1, poolFree = conn : (poolFree pool) }

withConn connPool = bracket (takeConn connPool) (putConn connPool)
于 2011-02-04T09:24:59.337 に答える