PHP和pgbouncer处于事务模式:当前事务被中止

发布于 2024-11-17 00:46:52 字数 4892 浏览 0 评论 0原文

我运行一个 Drupal 7.2 网站,其中嵌入 一个 Flash 游戏,并为玩家提供了一些自定义 PHP 脚本统计数据。使用 CentOS 5.6/64 位、PostgreSQL 8.4.8 和 PHP 5.3。它是一款配备 4GB RAM 的四核处理器。

在高峰时期(大约有 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

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

这很有帮助,但我经常遇到一个问题,即找不到准备好的 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;
}

I run a Drupal 7.2 web site embedding a flash game with few custom PHP scripts for the player stats. Using CentOS 5.6/64 bit, PostgreSQL 8.4.8 and PHP 5.3. It is a Quad-Opteron with 4GB RAM.

At the peak times (when there are around 500 players online) my web site used to went down with too many postmaster processes. On the advice of pgsql-general mailing list I have installed pgbouncer 1.3.4 with the following /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

And have increased shared_buffers = 1024MB and decreased max_connections = 50 in postgresql.conf.

This has helped, but I have often a problem that a prepared PDO statement won't be found:

SQLSTATE[26000]: Invalid sql statement name: 7 ERROR: prepared statement "pdo_stmt_00000016" does not exist
  • probably because pgbouncer switches the connection between a prepare() and execute().

I can't switch pgbouncer to session mode - my web site will hang.

I've tried adding PDO::ATTR_EMULATE_PREPARES => true - my web site hangs too.

I've added beginTransaction() and commit() around each prepare() and execute() call - but then I get the following error very often:

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

Below is an excerpt of my code failing with that error - it is very straightforward and just calls five SELECT statements:

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 ? '' : ' »') . '<br />';
                }
                $db->commit();

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

        return $table;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情栀口红 2024-11-24 00:46:52

我不使用 PDO,但在会话模式下使用 pgBouncer 准备好的语句对我来说确实有用。我只需要设置“server_reset_query = DISCARD ALL”即可使 preapred 语句正常工作。您可以将 pool_mode 设置为 session 并设置上述变量吗?

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?

虫児飞 2024-11-24 00:46:52

事务池

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

Transaction pooling

To make prepared statements work in this mode would need PgBouncer to
keep track of them internally, which it does not do. So only way to
keep using PgBouncer in this mode is to disable prepared statements
completely.

我纯我任性 2024-11-24 00:46:52
  1. 配置 pgbouncer 来使用事务池
  2. 编写一个 PL 函数来创建您的 PREPAREed 语句
  3. 让您的 PL 函数检查 pg_prepared_statements 系统视图和生成所有准备好的语句(如果有遗漏)。
  4. 将 SQL 命令执行更改为:
    1. 开始
    2. SELECT create_prepared_statements();
    3. /* 做你通常会做的任何事情 */
    4. 提交

您需要调用这个尚未编写的 create_prepared_statements() PL 函数的原因是您不知道您的连接将被分派到哪个后端,也不知道您是否是后端说to 是新生成的,没有 PREPARE 语句。

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

  1. Configure pgbouncer to use transaction pooling
  2. Write a PL function that creates your PREPAREed statements
  3. Have your PL function check the pg_prepared_statements system view and generate all of your prepared statements if any are missing.
  4. Change your SQL command execution to be:
    1. BEGIN
    2. SELECT create_prepared_statements();
    3. /* Do whatever it is that you would normally do */
    4. COMMIT

The reason you need to call this yet-to-be-written create_prepared_statements() PL function is because you do not know which backend your connection is being dispatched to or whether or not the backend you are talking to was freshly spawned and has no PREPAREed statements.

Depending on how you're using your PREPARE'ed statements, look at VIEW's or PL functions since they automatically generate and cache PREPARE'ed statements. I'd suggest making more aggressive use of PL/pgsql functions, however since that's the easiest method to maintain.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文