本文目录导读:

在 PHP 中,持久连接(Persistent Connection)主要应用于数据库连接场景,它的核心思想是:在一次请求结束后,不关闭与数据库的连接,而是将其放入连接池中,供后续请求复用,从而避免每次请求都重新建立 TCP 连接的开销。
PHP 的持久连接机制与 Java 或 Python 等常驻内存的语言不同——PHP 本身是“请求-响应”模式,每个请求结束后内存会释放,PHP 的持久连接通常由扩展内部通过进程池或连接池实现。
常见数据库的持久连接实现
1 MySQL(mysqli / PDO)
mysqli 示例:
// 在主机名前加 "p:" 前缀
$db = new mysqli('p:localhost', 'user', 'pass', 'dbname');
PDO 示例:
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
PDO::ATTR_PERSISTENT => true
]);
核心逻辑:
- 连接关闭时不会真正断开,而是返回连接池。
- 同一个
host:port:user的后续请求会复用该连接。 - 连接失效时(如 MySQL 超时关闭),扩展会自动清理。
⚠️ 注意事项:
- 不要混用持久连接和非持久连接(可能导致连接泄漏)。
- 使用持久连接时,必须显式处理事务回滚,否则残留事务会影响下一个请求。
- PHP-FPM 模式下,持久连接会关联到进程:同一 worker 进程的多个请求可复用连接。
2 PostgreSQL(pgsql / PDO_PGSQL)
pgsql 扩展:
$conn = pg_pconnect("host=localhost dbname=test user=user password=pass");
或使用 pg_connect 带 PGSQL_CONNECT_FORCE_NEW 常量。
PDO_PGSQL:
$db = new PDO('pgsql:host=localhost;dbname=test', 'user', 'pass', [
PDO::ATTR_PERSISTENT => true
]);
3 Redis / Memcached(非数据库持久连接)
- Redis:
phpredis扩展支持pconnect()方法,连接在进程生命周期内保持。 - Memcached:构造函数中指定
persistent_id实现复用。
PHP 持久连接的底层原理
PHP 本身不是常驻内存的,但 PHP 扩展(如 mysqli、pdo_mysql)可以在内部维护连接池,当启用 p: 或 PDO::ATTR_PERSISTENT 时:
- 请求开始 → 检查连接池中是否有匹配的空闲连接(匹配条件:主机、端口、用户名、数据库名)。
- 有连接 → 复用(但需验证连接是否有效,无效则重建)。
- 无连接 → 创建新连接并加入池。
- 请求结束 → 不调用
mysql_close()或pg_close(),而是将连接标记为空闲,放回池中。 - 下次同一进程的请求 → 从池中取出连接(跳过 TCP 握手)。
注意:连接池属于 PHP 进程级别,而非跨进程共享。
- 使用
mod_php(Apache 模块)时,连接池随 Apache 子进程存活。 - 使用
PHP-FPM时,连接池随 FPM worker 进程存活。
何时使用持久连接?
✅ 推荐场景
- 高并发、短连接频繁建立(如大量 API 请求)。
- 数据库连接开销大(如使用 SSL、高延迟网络)。
- PHP-FPM 工作进程数相对稳定(如 50~200 个进程)。
❌ 不建议的场景
- 每个用户使用不同数据库账号(连接池无法复用)。
- 数据库有连接数限制(持久连接会占用更多连接)。
- 使用事务时未显式回滚(残留锁或事务影响后续请求)。
- 使用长连接池管理工具(如 pgbouncer、ProxySQL) 时,应用层持久连接可能无意义。
常见坑与解决方案
1 连接死锁或断开
- 现象:MySQL
wait_timeout或interactive_timeout后,PHP 进程尝试复用已断开的连接。 - 解决:启用
PDO::ATTR_PERSISTENT时,PDO 内部会执行SET AUTOCOMMIT=1和ping()检测,无效则重建。
2 事务残留
- 问题:上一个请求未提交事务,下一个请求继续使用该连接,导致脏数据。
- 解决:在请求结束时主动回滚事务。
3 连接数膨胀
- 问题:每个 PHP-FPM worker 进程都持有独立连接,总连接数 = 进程数 × 每进程连接数。
- 解决:限制 FPM
pm.max_children,或使用连接池中间件(如 ProxySQL)。
持久连接的真实性能收益
| 场景 | 非持久连接 | 持久连接 | 收益 |
|---|---|---|---|
| 每次请求只执行一次简单查询 | ~2ms 建立 + 0.1ms 执行 | 1ms 执行 | 显著 |
| 每次请求执行 10 次复杂查询 | 2ms 建立 + 50ms 执行 | 0ms 建立 + 50ms 执行 | 4% |
| 数据库在本地(Unix Socket) | 1ms 建立 | 0ms 建立 | 可忽略 |
当网络延迟较高(如云服务器跨可用区、外网数据库)或查询极短时,收益最明显。
- 持久连接 ≠ 连接池:PHP 持久连接本质是进程级连接复用,而非真正跨进程的共享连接池。
- 使用方式:加
p:前缀(mysqli)或PDO::ATTR_PERSISTENT(PDO)。 - 核心注意:事务清理、连接有效性验证、连接数控制。
- 替代方案:如果连接池管理复杂度高,考虑使用数据库中间件(如 ProxySQL、PgBouncer)。
如果需要进一步了解特定场景(如 Laravel/Symfony 中的持久连接配置),可以告诉我具体框架,我可以给出更详细的代码示例。