PHP 还有第三种情况:在 fastCGI 接口上运行。在这种情况下,PHP 进程在每次请求后不会被销毁,因此持久连接会持久存在。设置 PHP_FCGI_CHILDREN << mysql's max_connections,你将没事。
持久连接是指在脚本执行结束后不会关闭的链接。当请求持久连接时,PHP 会检查是否存在相同的持久连接(从之前保持打开状态的连接),如果存在,则使用它。如果不存在,则创建链接。一个“相同的”连接是指连接到同一个主机,使用相同的用户名和相同的密码(如果适用)。
对 Web 服务器工作方式和负载分配不太熟悉的人可能会误解持久连接。特别地,它们不能让你在一个链接上打开“用户会话”,它们不能让你有效地构建事务,并且它们也不能做很多其他事情。事实上,为了非常清楚地说明这个问题,持久连接不能提供任何非持久连接无法提供的功能。
为什么?
这与 Web 服务器的工作方式有关。你的 Web 服务器可以使用 PHP 生成网页的方式有三种。
第一种方法是使用 PHP 作为 CGI “包装器”。以这种方式运行时,PHP 解释器的一个实例将在每次对 Web 服务器的页面请求(对于 PHP 页面)时创建和销毁。由于它在每次请求后都会被销毁,它获取的任何资源(例如 SQL 数据库服务器的链接)都会在它被销毁时关闭。在这种情况下,你不会从尝试使用持久连接中获得任何好处——它们根本不会持久。
第二种,也是最流行的方法,是在多进程 Web 服务器中运行 PHP 作为模块,目前仅限于 Apache。多进程服务器通常有一个进程(父进程)协调一组实际执行提供网页工作的进程(其子进程)。当来自客户端的请求进来时,它会被传递给一个尚未为其他客户端提供服务的子进程。这意味着,当同一个客户端对服务器发出第二个请求时,它可能由与第一次不同的子进程提供服务。在打开持久连接时,每个后续请求 SQL 服务的页面都可以重用与 SQL 服务器的相同已建立连接。
最后一种方法是将 PHP 作为多线程 Web 服务器的插件使用。目前 PHP 支持 WSAPI 和 NSAPI(在 Windows 上),它们都允许 PHP 作为 Netscape FastTrack(iPlanet)、Microsoft 的 Internet Information Server (IIS) 和 O'Reilly 的 WebSite Pro 等多线程服务器的插件使用。行为基本上与之前描述的多进程模型相同。
如果持久连接没有任何额外功能,那它们有什么用?
答案非常简单——效率。如果与 SQL 服务器建立连接的开销很高,那么持久连接会有所帮助。这种开销是否真的很高取决于许多因素。例如,数据库的类型,它是否与你的 Web 服务器位于同一台计算机上,SQL 服务器所在的机器的负载如何等等。最重要的是,如果连接开销很高,持久连接会极大地帮助你。它们会导致子进程在其整个生命周期内仅连接一次,而不是每次处理需要连接到 SQL 服务器的页面时都连接一次。这意味着每个打开了持久连接的子进程都会与服务器建立自己的打开的持久连接。例如,如果你有 20 个不同的子进程运行了一个脚本,该脚本与你的 SQL 服务器建立了持久连接,那么你将与 SQL 服务器建立 20 个不同的连接,每个子进程一个。
但是,请注意,如果你使用的是连接限制被持久子进程连接超过的数据库,这可能会有一些缺点。如果你的数据库限制为 16 个同时连接,并且在一个繁忙的服务器会话过程中,17 个子线程尝试连接,那么其中一个将无法连接。如果你的脚本中存在不允许连接关闭的错误(例如无限循环),那么只有 16 个连接的数据库可能会很快被淹没。请查看你的数据库文档,了解如何处理被遗弃或闲置的连接。
在使用持久连接时,需要牢记几个额外的注意事项。一个是,当在一个持久连接上使用表锁定时,如果脚本由于任何原因无法释放锁定,那么随后使用相同连接的脚本将无限期地阻塞,你可能需要重新启动 httpd 服务器或数据库服务器。另一个是,当使用事务时,如果脚本执行在事务块结束之前结束,那么事务块也会延续到下一个使用该连接的脚本。在这两种情况下,你都可以使用 register_shutdown_function() 注册一个简单的清理函数来解锁你的表或回滚你的事务。更重要的是,通过不在使用表锁定或事务的脚本中使用持久连接来完全避免这个问题(你仍然可以在其他地方使用它们)。
重要的总结。持久连接的设计目的是与常规连接一一对应。这意味着你始终可以将持久连接替换为非持久连接,并且不会改变脚本的行为方式。它可能(并且很可能)会改变脚本的效率,但不会改变它的行为!
另请参见 ibase_pconnect()、ociplogon()、odbc_pconnect()、oci_pconnect()、pfsockopen() 和 pg_pconnect()。
PHP 还有第三种情况:在 fastCGI 接口上运行。在这种情况下,PHP 进程在每次请求后不会被销毁,因此持久连接会持久存在。设置 PHP_FCGI_CHILDREN << mysql's max_connections,你将没事。
关于 odbc_pconnect 以及可能的其他 pconnect 变体的另一个需要注意的地方
如果连接遇到错误(无效 SQL、不正确的请求等),该错误将返回,并且在对该连接的每次后续操作中都存在,即使后续操作没有导致其他错误。
例如
一个脚本使用 odbc_pconnect 连接。
连接在第一次使用时创建。
脚本调用一个查询“Select * FROM Table1”。
Table1 不存在,并且 odbc_errormsg 包含该错误。
稍后(可能是几天后),使用相同的 odbc_pconnect 参数调用不同的脚本。
连接已经存在,因此被重用。
脚本调用一个查询“Select * FROM Table0”。
查询运行良好,但 odbc_errormsg 仍然返回有关 Table1 不存在的错误。
我无法找到使用 odbc_ 函数清除该错误的方法,因此请注意此问题或使用 odbc_connect 代替。
在 IBM_DB2 扩展 v1.9.0 或更高版本中,在每个请求结束时对持久连接执行事务回滚,从而结束事务。这可以防止事务块在脚本执行在事务块结束之前结束的情况下延续到下一个使用该连接的请求。
似乎使用 pg_pconnect() 不会持久化临时视图/表。因此,如果你尝试使用查询结果创建临时视图/表,然后在同一个会话的下一个脚本中访问它们,那么你将无法做到。这些临时视图/表在每个 PHP 脚本结束时都会消失。解决此问题的一种方法是使用会话 ID 作为名称的一部分创建实际的视图/表,并将名称和创建时间的记录保存在一个公共表中。编写一个垃圾收集脚本,删除会话已过期的视图/表。
对于 oci8 扩展," [...] 当使用事务时,如果脚本执行在事务块结束之前结束,事务块也会延续到下一个使用该连接的脚本" 并不正确。oci8 扩展在使用持久连接的脚本结束时执行回滚,从而结束事务。回滚还会释放锁。但是,任何在持久连接上执行的 ALTER SESSION 命令(例如更改日期格式)都将保留到下一个脚本中。
对于使用 MySQL 并发现很多剩余的休眠进程的用户,请查看 MySQL 的 wait_timeout 指令。默认情况下,它被设置为 8 小时,但几乎所有像样的生产服务器都会将其降低到 60 秒的范围内。即使在我的测试服务器上,我也遇到了由于剩余的持久连接导致的太多连接的问题。
如果有人想知道为什么即使使用持久连接,空闲数据库进程(打开的连接)数量似乎还在增长,这里解释一下原因。
"你可能正在使用多进程 Web 服务器,例如 Apache。由于
数据库连接不能在不同的进程之间共享,如果请求碰巧来自不同的 Web 服务器,
则会创建一个新的
子进程。"
实际上你可以为连接提供一个端口,请查看 http://de2.php.net/manual/en/function.mysql-pconnect.php#AEN101879
只需将“主机名:端口”用作服务器地址。
如果在同一服务器上有多个数据库,并且使用持久连接,则必须在所有表名前面加上特定数据库的名称。
使用 xxx_select_db 函数更改数据库会更改所有共享该连接的用户连接的数据库(假设 PHP 在共享模式下运行,而不是 CGI/CLI)。
如果你有两个数据库(实时数据库和存档数据库),并且你的脚本与这两个数据库交互,则不能使用两个持久连接并分别更改每个连接的数据库。
即使没有指定要使用持久连接,内部也会使用持久连接。这就是为什么在 mysql_connect/mssql_connect(PHPV4.2.0+)中添加了 new_link 的原因。
这让我头疼了一段时间。似乎如果你在同一主机上运行多个数据库服务器(例如在多个端口上运行 MySQL),就不能使用 pconnect,因为端口号不是数据库连接的键的一部分。特别是如果你使用相同的用户名和密码连接到所有在不同端口上运行的数据库服务器。但这可能是 PHP-MySQL 特定的。你可能会得到与你要求的端口完全不同的端口的连接。