情景分析
脚本代码:
try {
$dbh1 = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
} catch (PDOException $e) {
exit('连接数据库失败1');
} finally {
echo "连接成功1\n";
}
try {
$dbh2 = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
} catch (PDOException $e) {
exit('连接数据库失败2');
} finally {
echo "连接成功2\n";
}
try {
$dbh3 = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
} catch (PDOException $e) {
exit('连接数据库失败3');
} finally {
echo "连接成功3\n";
}
echo "保持连接中...\n";
sleep(10);
echo "执行结束\n";
CLI执行:
root@78ad0df34cef:/home/www/test# php index.php
连接成功1
连接成功2
连接成功3
保持连接中...
执行结束
在脚本sleep过程中,我们查看mysql的连接信息:
mysql> show processlist;
+----+------+-----------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+----------+------------------+
| 3 | root | localhost | NULL | Query | 0 | starting | show processlist |
| 24 | root | localhost | test | Sleep | 6 | | NULL |
| 25 | root | localhost | test | Sleep | 6 | | NULL |
| 26 | root | localhost | test | Sleep | 6 | | NULL |
+----+------+-----------+------+---------+------+----------+------------------+
4 rows in set (0.00 sec)
可以看到一个脚本的执行产生了三个数据库连接,但是如果将后面的实例化的pdo实例赋值给之前实例化的pdo实例,则新的连接会替换掉前一个连接,而不会产生新的连接。所以我们在编程过程中,应该避免多次实例化pdo,而产生不必要的数据库性能消耗。
解决方案
封装一个单例模式的类,该类实例化的过程就是创建pdo连接的过程。我们要建立数据库连接时,不是手动实例化pdo,而是去获取这个类的实例。
实例化pdo类时,设定 持久连接 参数:
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(
PDO::ATTR_PERSISTENT => true
));
?>
PHP官方文档的引用:
很多 web 应用程序通过使用到数据库服务的持久连接获得好处。持久连接在脚本结束后不会被关闭,且被缓存,当另一个使用相同凭证的脚本连接请求时被重用。持久连接缓存可以避免每次脚本需要与数据库回话时建立一个新连接的开销,从而让web 应用程序更快。
官方所说的脚本结束,在fpm模式下就是指一次客户端请求的结束。另一个使用相同凭证的脚本也就可以对应成另一个使用相同数据库连接凭证的客户端请求。首先我们要知道,这两次客户端的请求是根据fpm-workers的空闲情况,被分配给某个worker去执行的,所以两次请求被分配到同一个worker的可能性很低。接着,我们阐明下面的情景。
开启持久连接之后,数据库连接是被缓存于fpm进程之中的。如果某个fpm-worker进程中已经缓存了持久连接,此时可能出现如下两种情况:
当脚本中再次执行带 ATTR_PERSISTENT 参数的pdo连接时,会复用之前的连接,而不会产生新的连接。
当脚本中再次执行不带 ATTR_PERSISTENT 参数的pdo连接时,还会再次产生一个新的数据库连接。