pdo持久连接必须在构造时通过options参数传入pdo::attr_persistent => true,且dsn、用户名、密码等参数严格一致才可复用;它按php-fpm进程隔离,非跨进程连接池,需配合proxysql等中间件实现真正连接池化。

PDO::ATTR_PERSISTENT => true 必须在构造时传入,否则无效
持久连接不是“打开开关”就能复用的魔法功能,它依赖于 PHP-FPM 进程生命周期和连接参数的严格一致性。最常见错误是:在 $pdo = new PDO(...) 之后再调用 $pdo->setAttribute(PDO::ATTR_PERSISTENT, true) —— 这完全不起作用。PDO 驱动只在初始化阶段读取该选项。
- 必须把
PDO::ATTR_PERSISTENT => true放进第四个参数(options 数组)中,像这样:new PDO($dsn, $user, $pass, [PDO::ATTR_PERSISTENT => true]) - DSN、用户名、密码、端口、socket 路径,哪怕一个字符不同(比如
localhost和127.0.0.1),都会被视为“不同连接”,无法复用 - 如果应用里混用了
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true和false的配置,PDO 会为它们分别维护独立的持久连接池,实际连接数可能翻倍
mysqli 持久连接靠主机名前加 p:,但自动清理有代价
mysqli 不提供单独的 mysqli_pconnect() 函数,而是通过修改主机名实现:mysqli_connect('p:localhost', ...)。这点容易被忽略,尤其从旧 mysql_* 扩展迁移过来时。
- 所有通过
p:localhost建立的连接,在脚本结束时不会关闭,会被缓存到当前 PHP-FPM worker 进程内 - mysqli 内置了连接复用前的自动清理逻辑(回滚事务、删临时表、解锁等),避免状态污染,但每次复用都要执行这些操作,带来额外开销
- 若确定业务逻辑已做好显式清理(比如每请求末尾都
$mysqli->rollback()+$mysqli->close()),可编译时加MYSQLI_NO_CHANGE_USER_ON_PCONNECT宏禁用自动清理,提升复用速度
持久连接不等于连接池,别指望跨进程共享
PHP 本身没有全局连接池。每个 PHP-FPM worker 进程维护自己的一组持久连接,彼此隔离。这意味着:
- 100 个
pm.max_children = 100的 FPM 进程,最多可能持有 100 个相同 DSN 的 MySQL 连接,哪怕只有一两个并发请求 - MySQL 的
max_connections必须 ≥ FPM 最大子进程数 × 单进程最大持久连接数(通常为 1),否则很快出现Too many connections - CLI 脚本或 cron 任务中使用持久连接毫无意义——进程退出即销毁,根本不会复用
memcached 和 MySQL 的持久机制完全不同,别套用同一套理解
Memcached 扩展的持久连接靠 Memcached 构造函数的 persistent_id 参数控制,而 MySQL 的 pconnect 或 PDO::ATTR_PERSISTENT 是连接层行为。两者底层机制不兼容,不能混用逻辑。
- Memcached 的
persistent_id是 PHP 进程内对象级复用,不涉及网络连接保持;而 MySQL 持久连接是真实 TCP 连接不关闭 - 误在 Memcached 实例上重复调用
addServers()(没检查getServerList()),会导致单个 persistent_id 下服务器列表无限膨胀,最终连接超时或崩溃 - MySQL ProxySQL / MySQL Router 这类中间件才是真正的连接池,它们能跨 PHP 进程统一管理连接数、路由、熔断;原生 PHP 持久连接只是“省一次握手”,不是“池化资源”
真正要压降数据库连接数,得从架构层面引入 ProxySQL 并配好 mysql-pool 策略,而不是只靠 PHP 层加个 true。否则你调小了 pm.max_children,性能掉得比连接数还快。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











