2

我运行了一个 Drupal 7.2 网站,该网站嵌入了一个带有少量自定义 PHP 脚本的 flash 游戏,用于玩家统计数据。使用 CentOS 5.6/64 位、PostgreSQL 8.4.8 和 PHP 5.3。它是具有 4GB RAM 的 Quad-Opteron。

在高峰时期(大约有 500 名玩家在线),我的网站曾经因过多的 postmaster 进程而崩溃。根据pgsql-general 邮件列表的建议,我安装了 pgbouncer 1.3.4 和以下 /etc/pgbouncer.ini:

[databases]
pref = host=/tmp user=pref password=XXX dbname=pref

[pgbouncer]
logfile = /var/log/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
listen_port = 6432
unix_socket_dir = /tmp

auth_type = md5
auth_file = /var/lib/pgsql/data/global/pg_auth

pool_mode = transaction
;pool_mode = session

server_check_delay = 10

max_client_conn = 200
default_pool_size = 16

log_connections = 0
log_disconnections = 0
log_pooler_errors = 1

并且在 postgresql.conf 中增加了 shared_buffers = 1024MB 并减少了 max_connections = 50。

这有帮助,但我经常遇到一个问题,即找不到准备好的 PDO 语句:

SQLSTATE[26000]: Invalid sql statement name: 7 ERROR: prepared statement "pdo_stmt_00000016" does not exist
  • 可能是因为 pgbouncer 切换了 prepare() 和 execute() 之间的连接。

我无法将 pgbouncer 切换到会话模式 - 我的网站将挂起。

我尝试添加PDO::ATTR_EMULATE_PREPARES => true - 我的网站也挂起。

我在每个 prepare() 和 execute() 调用周围添加了 beginTransaction() 和 commit() - 但是我经常收到以下错误:

SQLSTATE[25P02]: In failed sql transaction: 7 ERROR:  current transaction is aborted, commands ignored until end of transaction block

下面是我的代码因该错误而失败的摘录 - 它非常简单,只需调用五个 SELECT 语句:

function fetch_top() {
        $table       = '';
        $top         = '';

        try {
                # throw exception on any errors
                $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
                $db = new PDO(sprintf('pgsql:host=%s port=%u; dbname=%s',
                        DBHOST, DBPORT, DBNAME), DBUSER, DBPASS, $options);

                # last week's winner
                $db->beginTransaction();
                $sth = $db->prepare("
select u.id,
        u.first_name,
        u.avatar,
        u.female,
        u.city,
        m.money,
        u.login > u.logout as online
from pref_users u, pref_money m where
        m.yw=to_char(current_timestamp - interval '1 week', 'IYYY-IW') and
        u.id=m.id
order by m.money desc
limit 1
");
                $sth->execute();
                $winner = $sth->fetch(PDO::FETCH_OBJ);
                $db->commit();

                $db->beginTransaction();
                $sth = $db->prepare('
select count(id) from (
    select id,
           row_number() over(partition by yw order by money desc) as ranking
    from pref_money
) x
where x.ranking = 1 and id=?
');
                $sth->execute(array($winner->id));
                $winner_medals = $sth->fetchColumn();
                $db->commit();

                # current week leader
                $db->beginTransaction();
                $sth = $db->prepare("
select u.id,
        u.first_name,
        u.avatar,
        u.female,
        u.city,
        m.money,
        u.login > u.logout as online
from pref_users u, pref_money m where
        m.yw=to_char(current_timestamp, 'IYYY-IW') and
        u.id=m.id
order by m.money desc
limit 1
");
                $sth->execute();
                $leader = $sth->fetch(PDO::FETCH_OBJ);
                $db->commit();

                $db->beginTransaction();
                $sth = $db->prepare('
select count(id) from (
    select id,
           row_number() over(partition by yw order by money desc) as ranking
    from pref_money
) x
where x.ranking = 1 and id=?
');
                $sth->execute(array($leader->id));
                $leader_medals = $sth->fetchColumn();
                $db->commit();

                # fetch top players
                $db->beginTransaction();
                $sth = $db->prepare("
select u.id,
        u.first_name,
        u.female,
        u.city,
        m.money,
        u.login > u.logout as online
from pref_users u, pref_money m where
        m.yw=to_char(current_timestamp, 'IYYY-IW') and
        u.id=m.id
order by m.money desc
limit 7
");
                $sth->execute();
                $i = 0;
                while ($player = $sth->fetch(PDO::FETCH_OBJ)) {
                        $top .= user_link($player) . ($i++ > 0 ? '' : '&nbsp;&raquo;') . '<br />';
                }
                $db->commit();

                # create the HTML table
                $table = sprintf('.... skipped for brevity ....');
        } catch (Exception $e) {
                exit('Database problem: ' . $e->getMessage());
        }

        return $table;
}

请问有什么帮助吗?亚历克斯

4

3 回答 3

1

交易池

要使准备好的语句在这种模式下工作,需要 PgBouncer 在内部跟踪它们,但它不会这样做。因此,在这种模式下继续使用 PgBouncer 的唯一方法是完全禁用准备好的语句。

于 2011-06-23T19:08:56.427 回答
1
  1. 配置pgbouncer使用transaction pooling
  2. 编写一个 PL 函数来创建您的PREPAREed 语句
  3. 让您的 PL 函数检查pg_prepared_statements系统视图并生成所有准备好的语句(如果缺少)。
  4. 将您的 SQL 命令执行更改为:
    1. BEGIN
    2. SELECT create_prepared_statements();
    3. /* Do whatever it is that you would normally do */
    4. COMMIT

您需要调用这个尚未编写的create_prepared_statements()PL 函数的原因是因为您不知道您的连接被分派到哪个后端,或者您正在与之交谈的后端是否是新生成的并且没有PREPAREed 语句。

根据您使用PREPARE'ed 语句的方式,查看VIEW's 或 PL 函数,因为它们会自动生成和缓存PREPARE'ed 语句。我建议更积极地使用 PL/pgsql 函数,但是因为这是最容易维护的方法。

于 2011-06-23T19:29:51.840 回答
1

I do not use PDO, but using prepared statements with pgBouncer in session mode does work for me. I just need to set "server_reset_query = DISCARD ALL" for the preapred statements to work correctly. Can you set pool_mode to session and also set the above mentioned variable?

于 2011-06-24T06:08:38.930 回答