PDO mysql外部连接陈旧在特定数量的秒
软件版本:
PHP 8.1.5 (cli)
mysql Ver 8.0.29-0ubuntu0.20.04.3 for Linux on x86_64 ((Ubuntu))
将我们的数据库迁移到新服务器和新软件后,我注意到了奇怪的行为,可以通过以下简化的代码段显示:
<?php
$host = '<remote host (external IP)>';
$db = '<db name>';
$user = '<user>';
$pass = '<password>';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_TIMEOUT => 5
];
try {
//executing first statement, no problem
echo 'CREATING PDO OBJECT'.PHP_EOL;
$pdo = new PDO($dsn, $user, $pass, $options);
echo 'PDO OBJECT CREATED, PREPARING STATEMENT'.PHP_EOL;
$stmt = $pdo->prepare('SELECT *
FROM someObject
WHERE objectID = 1');
echo 'STATEMENT PREPARED, EXECUTING STATEMENT'.PHP_EOL;
$stmt->execute();
echo 'STATEMENT EXECUTED, FETCHING RESULT: ';
$result = $stmt->fetch();
echo count($result).PHP_EOL;
$sleep = 258;
echo 'SLEEP: '.$sleep.PHP_EOL;
sleep($sleep);
echo 'WOKE UP'.PHP_EOL;
//executing second statement after sleep, hangs
echo 'PREPARING STATEMENT'.PHP_EOL;
$stmt = $pdo->prepare('SELECT *
FROM someObject
WHERE objectID = 2'); //hangs here
echo 'STATEMENT PREPARED, EXECUTING STATEMENT'.PHP_EOL;
$stmt->execute();
echo 'STATEMENT EXECUTED, FETCHING RESULT: ';
$result = $stmt->fetch();
echo count($result).PHP_EOL;
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
此脚本输出是:
CREATING PDO OBJECT
PDO OBJECT CREATED, PREPARING STATEMENT
STATEMENT PREPARED, EXECUTING STATEMENT
STATEMENT EXECUTED, FETCHING RESULT: 20
SLEEP: 258
WOKE UP
PREPARING STATEMENT
之后,该过程变为陈旧而无能为力,准备语句是最后一个我在输出中看到的句子直到达到最大执行时间为止 - 然后由PHP删除该过程。如果逻辑达到数据库无效的258秒,它总是会发生。
如果我将睡眠时间减少到257秒 - 它总是有效的,我会看到第二个结果,并且脚本成功完成。对我来说,似乎有一些参数可以阻止257秒的不活动。 在MySQL方面,我看到了
SHOW FULL PROCESSLIST;
+------+-----------------+---------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+------+-----------------+---------------------+------------------+
| <id> | <user> | <host> | <db> | Sleep | 1258 | | NULL |
+------+-----------------+---------------------+------------------+
您可能看到的以下内容 - 时间是1258,它永远不会关闭(仅在达到MySQL Wait_TimeOut之后)。 下面的MySQL超时
+-----------------------------------+----------+
| Variable_name | Value |
+-----------------------------------+----------+
| connect_timeout | 10 |
| delayed_insert_timeout | 300 |
| have_statement_timeout | YES |
| innodb_flush_log_at_timeout | 1 |
| innodb_lock_wait_timeout | 50 |
| innodb_rollback_on_timeout | OFF |
| interactive_timeout | 28800 |
| lock_wait_timeout | 31536000 |
| mysqlx_connect_timeout | 30 |
| mysqlx_idle_worker_thread_timeout | 60 |
| mysqlx_interactive_timeout | 28800 |
| mysqlx_port_open_timeout | 0 |
| mysqlx_read_timeout | 30 |
| mysqlx_wait_timeout | 28800 |
| mysqlx_write_timeout | 60 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| replica_net_timeout | 60 |
| rpl_stop_replica_timeout | 31536000 |
| rpl_stop_slave_timeout | 31536000 |
| slave_net_timeout | 60 |
| ssl_session_cache_timeout | 300 |
| wait_timeout | 28800 |
+-----------------------------------+----------+
23 rows in set (0.00 sec)
您可能会说,如果不需要的话,DB连接不应保持打开状态,我同意这是解决此问题的原因。但是,在上一个服务器上,我没有注意到那样,想知道这里发生了什么。 请不要建议使用持久连接,我想找到原因,而不是解决后果。
Software versions:
PHP 8.1.5 (cli)
mysql Ver 8.0.29-0ubuntu0.20.04.3 for Linux on x86_64 ((Ubuntu))
After migrating our database to the new server and new software I noticed strange behaviour which could be shown with this simplified code snippet below:
<?php
$host = '<remote host (external IP)>';
$db = '<db name>';
$user = '<user>';
$pass = '<password>';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_TIMEOUT => 5
];
try {
//executing first statement, no problem
echo 'CREATING PDO OBJECT'.PHP_EOL;
$pdo = new PDO($dsn, $user, $pass, $options);
echo 'PDO OBJECT CREATED, PREPARING STATEMENT'.PHP_EOL;
$stmt = $pdo->prepare('SELECT *
FROM someObject
WHERE objectID = 1');
echo 'STATEMENT PREPARED, EXECUTING STATEMENT'.PHP_EOL;
$stmt->execute();
echo 'STATEMENT EXECUTED, FETCHING RESULT: ';
$result = $stmt->fetch();
echo count($result).PHP_EOL;
$sleep = 258;
echo 'SLEEP: '.$sleep.PHP_EOL;
sleep($sleep);
echo 'WOKE UP'.PHP_EOL;
//executing second statement after sleep, hangs
echo 'PREPARING STATEMENT'.PHP_EOL;
$stmt = $pdo->prepare('SELECT *
FROM someObject
WHERE objectID = 2'); //hangs here
echo 'STATEMENT PREPARED, EXECUTING STATEMENT'.PHP_EOL;
$stmt->execute();
echo 'STATEMENT EXECUTED, FETCHING RESULT: ';
$result = $stmt->fetch();
echo count($result).PHP_EOL;
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
This script output is:
CREATING PDO OBJECT
PDO OBJECT CREATED, PREPARING STATEMENT
STATEMENT PREPARED, EXECUTING STATEMENT
STATEMENT EXECUTED, FETCHING RESULT: 20
SLEEP: 258
WOKE UP
PREPARING STATEMENT
After this, the process becomes stale and does nothing, PREPARING STATEMENT is the last sentence I see in the output until max execution time is reached - then the process is dropped by PHP. It happens always if logic reaches 258 seconds of database inactivity.
If I reduce sleep time to 257 seconds - it always works, I see a second result, and the script finishes successfully. For me, it looks like there is some parameter that blocks connection after 257 seconds of inactivity.
On the MySQL side, I see following
SHOW FULL PROCESSLIST;
+------+-----------------+---------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+------+-----------------+---------------------+------------------+
| <id> | <user> | <host> | <db> | Sleep | 1258 | | NULL |
+------+-----------------+---------------------+------------------+
As you may see - Time is 1258 here, it never gets closed (only after it reaches MySQL wait_timeout).
MySQL timeouts below
+-----------------------------------+----------+
| Variable_name | Value |
+-----------------------------------+----------+
| connect_timeout | 10 |
| delayed_insert_timeout | 300 |
| have_statement_timeout | YES |
| innodb_flush_log_at_timeout | 1 |
| innodb_lock_wait_timeout | 50 |
| innodb_rollback_on_timeout | OFF |
| interactive_timeout | 28800 |
| lock_wait_timeout | 31536000 |
| mysqlx_connect_timeout | 30 |
| mysqlx_idle_worker_thread_timeout | 60 |
| mysqlx_interactive_timeout | 28800 |
| mysqlx_port_open_timeout | 0 |
| mysqlx_read_timeout | 30 |
| mysqlx_wait_timeout | 28800 |
| mysqlx_write_timeout | 60 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| replica_net_timeout | 60 |
| rpl_stop_replica_timeout | 31536000 |
| rpl_stop_slave_timeout | 31536000 |
| slave_net_timeout | 60 |
| ssl_session_cache_timeout | 300 |
| wait_timeout | 28800 |
+-----------------------------------+----------+
23 rows in set (0.00 sec)
You may say that the DB connection shouldn't be kept open if not needed and I agree with that, it is what I did to fix this problem. However, on the previous server, I noticed nothing like that and wondering what's happening here.
Please, don't suggest using the persistent connection, I want to find the reason, not fix consequences.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这最终在Azure一侧。
由于这两台服务器均托管在那里,所以我发现Azure防火墙认为TCP连接已经死了,并在4分钟后将其丢弃,如这里所述
https://learn.microsoft.com/en-us/azure/firewall/firewall/firewall-faq#what-is-the-tcp-idle-dcp-idle-thipe-for-azure-firewall
与任何连接有关,不仅与SQL ONE有关。
也许这对某人很有用,
可以通过更改TCP内核参数默认值来解决:
net.ipv4.tcp_keepalive_time到低于4分钟的任何值
This ended up being on the Azure side.
Since both servers were hosted there, I found that Azure Firewall considers TCP connection dead and drops it after 4 minutes of inactivity as stated here
https://learn.microsoft.com/en-us/azure/firewall/firewall-faq#what-is-the-tcp-idle-timeout-for-azure-firewall
It is about any connection, not only about SQL one.
Maybe this would be useful to someone
Could be fixed by changing TCP kernel parameters default value:
net.ipv4.tcp_keepalive_time to any value lower than 4 minutes