持久数据库连接

什么是持久连接?

持久数据库连接是指在脚本结束运行时不关闭的连接。当收到持久连接的请求时,PHP 将检查是否已经存在相同的持久连接(前面已经开启的)——如果存在,将直接使用这个连接。如果不存在,则建立新的连接。所谓“相同”的连接是指用相同的用户名和密码连接到相同主机的连接。

目前无法请求特定的连接,也无法保证获取的是现有连接还是全新的连接(如果所有现有连接都在使用中,或者请求由不同的 worker 进程处理,而该 worker 进程拥有独立的连接池)。

这意味着无法使用 PHP 持久连接来实现以下目的,例如:

  • 为特定的 Web 用户分配特定的数据库会话
  • 创建跨多个请求的大型事务
  • 在一个请求中发起查询并在另一个请求中收集结果

持久连接并没有提供非持久连接无法实现的任何功能。

Web 请求

web 服务器可以通过两种方法来利用 PHP 生成 web 页面。

第一种方法是将 PHP 自以为 CGI“包装器”用作一个单独运行的语言解释器(CGI Wapper)。当以这种方法运行时,PHP 会为向 web 服务器的每个 PHP 页面请求创建并销毁 PHP 解释器的时候实例。由于其会随每个请求的结束而销毁,因此其获取的任何资源(例如指向 SQL 数据库服务器的链接)都会在销毁时关闭。在这种情况下,不会从使用持久连接中获得任何好处——因为根本不会持久。

第二,也是最流行的方法是运行 PHP-FPM,或将 PHP 用作多进程 web 服务器的一个模块,目前只适用于 Apache。多进程的服务器通常有一个父进程和一组子进程协调运行,子进程负责提供网页的工作。每当接收达到客户端提出请求时,该请求会传递给尚未给其它客户端提供服务的某个子进程。这也就是说当相同的客户端第二次向服务端发出请求时,它将有可能由与第一次不同的某个子进程提供服务。在开启了一个持久连接后,所有请求 SQL 服务的后继页面都能够重用与 SQL 服务器建立的相同连接。

注意:

通过检查 phpinfo() 输出中的“Server API”值,或者在 Web 请求中运行 PHP_SAPI 的值,可以确认 Web 请求所使用的方法。

如果服务器 API 为“Apache 2 Handler”或“FPM/FastCGI”,则持久连接将在同一 worker 进程处理的请求之间使用。对于其他任何值,持久连接在每次请求后将不会保持。

命令行进程

由于命令行 PHP 为每个脚本使用新进程,持久连接不会在命令行脚本之间共享,因此在诸如定时任务或命令之类的临时脚本中使用持久连接并无意义。然而,如果正在编写长时间运行的应用服务器,该服务器处理大量请求或任务,并且每个任务可能需要各自的数据库连接,那么持久连接可能会很有用。

为何使用它们?

如果创建链接到服务器的开销很高,则持久连接是好的。开销高低取决于很多因素。例如,数据库的类型,数据库服务和 web 服务是否在同一台服务器上,SQL 服务器负载状况等。事实是,如果连接开销很高,持久连接将显著的提高效率。它使得子进程在其整个生命周期中只做一次连接操作,而非每次在处理页面时都要向 SQL 服务器提出连接请求。这意味着,每个打开持久连接的子进程将对服务器建立各自的持久连接。例如,如果有 20 个不同的子进程运行与 SQL 服务器建立持久连接的脚本,那么将有 20 个与 SQL 服务器建立的不同连接,每个进程一个。

潜在缺点:连接限制

注意,如果持久连接的数量超过了数据库设定的连接数限制,系统将会产生一些问题。如果数据库的同时连接数限制为 16,而在繁忙会话的情况下,有 17 个线程试图连接,那么有一个线程将无法连接。如果这个时候,在脚本中出现了不允许关闭连接的错误(例如无限循环),则该数据库的 16 个连接将迅速地受到影响。请查阅数据库文档以获取关于如何处理废弃及空闲连接的信息。

持久连接通常会增加任意时刻的打开连接数,因为处于空闲状态的 worker 进程仍会保留其之前处理请求时所建立的连接。如果启动了大量 worker 进程以应对请求激增,这些进程所打开的连接将会一直保留,直到 worker 进程终止或数据库服务器关闭连接为止。

确保数据库服务器允许的最大连接数大于 Web 请求工作进程的最大数量(以及任何其他用途,例如定时任务或管理连接)。

查阅数据库文档以获取有关处理遗弃或空闲连接(超时)的信息。较长的超时时间可能会显著增加任意时刻打开的持久连接数量。

潜在缺点:维护连接状态

某些数据库扩展在重用连接时会自动执行清理操作,而其他扩展则将此任务交由应用程序开发者决定。根据所选的数据库扩展和应用程序设计,可能需要在脚本退出前进行手动清理。可能导致连接处于意外状态的更改包括:

  • 已选择或默认的数据库
  • 表锁
  • 未提交的事务
  • 临时表
  • 与连接相关的特定设置或功能(例如性能分析)

未清理或未关闭的表锁和事务可能会导致无限期阻塞其他查询,或导致连接的后续重用引发意外更改。

选择了错误的数据库将导致连接的后续重用无法按预期执行查询(或者在数据库模式足够相似的情况下,可能会对错误的数据库执行查询)。

如果未清理临时表,后续请求将无法重新创建相同的表。

可以使用类析构方法或 register_shutdown_function() 来实现清理操作。还可以考虑使用包含此功能的专用连接池代理。

结语

鉴于上述行为及潜在缺点,不应未经慎重考虑就使用持久连接。在没有对应用程序进行额外更改,以及未对数据库服务器和 Web 服务器及/或 PHP-FPM 进行谨慎配置的情况下,不应使用持久连接。

可以考虑其他解决方案,例如调查并修复导致连接创建开销的原因(例如,在数据库服务器上禁用反向 DNS 查找),或使用专用的连接池代理。

对于高流量的 Web API,可以考虑使用其他运行时环境或长时间运行的应用服务器。