在PDO中使用持久连接有哪些缺点


181

在PDO中,可以使用PDO::ATTR_PERSISTENT属性使连接持久化。根据PHP手册-

持久连接不会在脚本结尾处关闭,而是在另一个脚本使用相同凭据请求连接时被缓存并重新使用。持久性连接缓存使您避免每次脚本需要与数据库进行通信时建立新连接的开销,从而加快了Web应用程序的速度。

该手册还建议不要在使用PDO ODBC驱动程序时使用持久连接,因为这可能会妨碍ODBC连接池过程。

因此,除了最后一种情况外,在PDO中使用持久连接似乎没有任何缺点。但是,我想知道使用此机制是否还有其他缺点,即该机制导致性能下降或类似情况的情况。


哇,您为这个简单的问题支付了1000代表奖金?
起搏器

@Pacerier,不,是别人
查尔斯

Answers:


287

请务必阅读以下答案,其中详细介绍了缓解此处概述的问题的方法。


使用PDO与进行持久连接的任何其他PHP数据库接口一样,也存在相同的缺点:如果您的脚本在数据库操作过程中意外终止,则下一个获得剩余连接的请求将在死脚本停止的地方继续。连接在进程管理器级别(Apache for mod_php,如果使用的是FastCGI,则为当前的FastCGI进程,等等)保持打开状态,而不是在PHP级别保持打开状态,并且PHP不会告诉父进程何时终止连接脚本异常终止。

如果死脚本锁定了表,则这些表将保持锁定,直到连接死亡或下一个获得连接的脚本将表本身解锁为止。

如果死脚本位于事务中间,则可能会阻塞多个表,直到死锁计时器开始运行为止,即使这样,死锁计时器也可以杀死较新的请求,而不是导致问题的较旧的请求。

如果无效脚本位于事务中间,则下一个获得该连接的脚本也将获得事务状态。下一个脚本很有可能(取决于您的应用程序设计)实际上不尝试提交现有事务,或者在不应该存在时将提交,而在不应该存在时回退。

这只是冰山一角。通过在每个脚本请求上进行肮脏的连接后始终尝试进行清理,可以在一定程度上缓解所有问题,但这取决于数据库,可能会很麻烦。除非您已将创建数据库连接确定为脚本中的瓶颈(这意味着您已经使用xdebug和/或xhprof完成了代码分析),否则不应将持久连接视为解决任何问题的方法。

此外,大多数现代数据库(包括PostgreSQL)都有自己喜欢的执行连接池的方式,它们没有直接基于普通PHP的持久性连接所具有的直接缺点。


为了阐明这一点,我们在工作场所使用持久连接,但不是出于选择。我们遇到了奇怪的连接行为,即从我们的应用程序服务器到数据库服务器的初始连接正好花费三秒钟,而这本该花费的时间仅为几分之一秒。我们认为这是一个内核错误。我们放弃尝试对其进行故障排除,因为它是随机发生的并且无法按需复制,并且我们的外包IT没有具体的能力来对其进行跟踪。

无论如何,当仓库中的人员正在处理几百个进来的零件,并且每个零件要花三分半秒而不是半秒的时间时,我们必须采取行动,然后他们才绑架我们所有人,并让我们帮助他们。因此,我们在自己的ERP / CRM / CMS怪诞中花了些力气,并亲身经历了所有持久连接的恐怖。我们花了几周的时间来追踪所有看似随机发生的细微问题和奇怪行为。事实证明,我们的用户孜孜不倦地挤出应用程序的那些每周一次的致命错误正在留下锁定的表,被放弃的交易和其他不幸的不稳定状态。

这个糟糕的故事有一个意义:它以性能为名打破了我们从未料想打破的事情。 权衡是不值得的,我们热切地等待着有一天我们可以切换回普通连接而不会引起用户骚动。


2
我希望我在跑步前已经读过此答案SELECT orders.* FROM orders LEFT JOIN items USING(item_id)
Ast Derek 2010年

31
我知道一个大型网站已经使用持续连接近十年了。技巧是在数据库扩展上方使用一层,并让它记住需要使用清除的内容register_shutdown_function()。如果进程终止,则连接也会终止。如果不是,则将连接重置为其干净状态(例如,回滚打开的事务)。如果失败,则连接将关闭,并且对同一进程的下一个请求将打开一个新的连接。无需妖魔化持久连接。
Walter Tross 2013年

我很好奇@Charles ...您的问题曾经解决过吗?
Tschallacka 2015年

@MichaelDibbets几个月前,我们更换了应用程序服务器,然后转为pconnect来查看是否还有3秒的错误。不是。我想这已经通过代理解决了。mysqli_change_user对于那些必须在不旨在处理状态问题的应用程序中进行持久连接的人们来说,下面的答案仍然可能是最好的解决方法。
2015年

5
我们的连接延迟了5秒,我们设法将其隔离为DNS + IPv6问题。服务器正在查找v6地址,但失败,然后使用IPv4地址。
奈杰尔·阿特金森

45

针对上述查尔斯的问题,

来源:http://www.php.net/manual/en/mysqli.quickstart.connections.php -

关于持久连接的一个常见抱怨是,在重用之前,它们的状态不会重置。例如,未完成和未完成的事务不会自动回滚。但是,也不会反映在将连接放入池与重新使用连接之间发生的授权更改。这可能被视为有害的副作用。相反,持久的名称可以理解为状态得以持久的保证。

mysqli扩展支持持久连接的两种解释:持久状态和重用之前的状态重置。默认为重设。在重用持久连接之前,mysqli扩展隐式调用mysqli_change_user()以重置状态。持久连接在用户看来就像刚刚打开一样。没有可见的先前用法的工件。

mysqli_change_user()功能是昂贵的操作。为了获得最佳性能,用户可能希望MYSQLI_NO_CHANGE_USER_ON_PCONNECT在设置了编译标志的情况下重新编译扩展名。

留给用户在安全行为和最佳性能之间进行选择。两者都是有效的优化目标。为了易于使用,已将安全行为设为默认行为,但会牺牲最大性能。


+1,如果不是因为我们已经以其他方式清理了混乱的事实,我很想看看手动调用change_user是否可以解决我们奇怪的未知状态问题。
查尔斯

PDO Postgres持久连接的等效项是什么?我有类似@Charles的问题,过了一会儿,用户会收到诸如获取sql之类的错误-服务器意外关闭了连接,这可能意味着在运行简单的SELECT查询(甚至没有事务)时,服务器异常终止。
Carmageddon

1
@Carmageddon,这更适合一个新问题,但tl; dr是Postgres不执行pconnect,而您应该使用一个外部连接池。
查尔斯

@Charles,那是什么意思?使用PDO的持久连接是否等同于使用“外部连接池”?还是什么意思?
卡马吉登

@Carmageddon,我的意思是说Postgres社区将连接池定为比pconnect更好的解决方案。查看pgbouncer或pgpool-II。我不确定PDO是否仍会执行Postgres pconnect,但我可能完全不喜欢摇杆。
查尔斯

13

持久连接只有在(相对)较长的时间连接到数据库时才是一个好主意。如今,几乎从来没有这样。持久连接的最大缺点是,它限制了您可以浏览站点的用户数量:如果将MySQL配置为一次仅允许10个并发连接,则当第11个人尝试浏览您的站点时,它将无法使用。

PDO不管理持久性。MySQL驱动程序可以。当a)连接可用且主机/用户/密码/数据库匹配时,它将重用连接。如果有任何更改,它将不会重复使用连接。最好的实际效果是,您拥有的这些连接将如此频繁地启动和停止,因为您在站点上拥有不同的用户,并使它们保持不变并没有任何用处。

了解持久连接的关键是不要在大多数Web应用程序中使用它们。它们听起来很诱人,但它们很危险,几乎没有用。

我确定这上还有其他线程,但是持久连接很危险,因为它在请求之间保持不变。例如,如果您在请求期间锁定了表,然后又无法解锁,则该表将无限期保持锁定状态。持久连接对于99%的应用程序也几乎没有用,因为您无法知道是否会在不同请求之间使用相同的连接。每个Web线程将拥有自己的一组持久连接,并且您无法控制哪个线程将处理哪些请求。

PHP的过程mysql库具有一项功能,通过该功能,后续对mysql_connect的调用将返回相同的链接,而不是打开不同的连接(正如您可能期望的那样)。这与持久连接无关,特定于mysql库。PDO没有表现出这种行为


资源链接:链接

通常,您可以将其用作粗略的“规则集”:

,如果满足以下条件,则使用持久连接:

  • 只有很少的应用程序/用户访问数据库,即,您不会导致200个打开(但可能是空闲)的连接,因为在同一主机上共享200个不同的用户。
  • 该数据库正在您通过网络访问的另一台服务器上运行

  • 一个(一个)应用程序经常访问数据库

NO,如果不使用永久连接,:

  • 您的应用程序每小时只需要访问数据库100次。

  • 您有许多访问同一台数据库服务器的Web服务器

使用持久连接的速度要快得多,尤其是在通过网络访问数据库时。如果数据库在同一台计算机上运行,​​并没有太大的区别,但是它仍然要快一些。但是-顾名思义-连接是持久的,即即使不使用它也保持打开状态。

问题在于,在“默认配置”中,MySQL仅允许1000个并行的“开放通道”。之后,将拒绝新的连接(您可以调整此设置)。因此,如果您有20个Web服务器,每个服务器上有100个客户端,并且每个服务器每小时只有一次页面访问,那么简单的数学就会告诉您,您需要2000个并行连接到数据库。那行不通。

Ergo:仅将其用于具有大量请求的应用程序。


4
下一行后,您的答案是从stackoverflow.com/a/51583/718224
Tony Stark

1
“是,使用持久连接,如果:[...]只有很少的应用程序/用户访问数据库”与“仅将其用于有大量请求的应用程序”相矛盾。后者是正确的。情况:每秒数千个请求将导致数百个活动数据库连接。当系统线性扩展时,它也会线性扩展与数据库的连接数量。因此,更多的请求(更多的用户)将导致更多的连接。因此,当您有很多请求(用户)时,您需要数量有限的活动连接!
Ken Van Hoeylandt,2015年

12

在测试中,我与本地主机的连接时间超过一秒钟,因此假设我应该使用持久连接。进一步的测试表明这是'localhost'的问题:

测试结果以秒为单位(由php microtime测量):

  • 托管的网站:connectDB:0.0038912296295166
  • 本地主机:connectDB:1.0214691162109(超过一秒钟:不要使用本地主机!)
  • 127.0.0.1:connectDB:0.00097203254699707

有趣的是:以下代码与使用127.0.0.1一样快:

$host = gethostbyname('localhost');
// echo "<p>$host</p>";
$db = new PDO("mysql:host=$host;dbname=" . DATABASE . ';charset=utf8', $username, $password,
    array(PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

好像PDO很难翻译域名!谢谢,我想知道为什么我的四核计算机上的每个连接都要花很长时间!
穆斯塔法

@Gunnar Bernstein +1很不错。“ localhost”当然需要更长的时间,这在某种程度上提高了我的Web应用程序的速度(它建立了大量连接)。
imperium2335

1
这很棒。我的开发机器上的解析有些问题...使用IP将我的脚本从6.1s升级到1.1s
Pete

localhost使用套接字连接,套接字连接因连接数量大而
著称

@mente是否有任何参考资料可以证明这一事实?我倾向于认为UDS比TCP更受青睐。谢谢。
纳克斯温

6

持久的连接应该可以大大提高性能。我不同意您应该“避免”坚持的主张。

听起来上面的抱怨是由使用MyIASM表并通过抓住表锁来入侵自己的交易版本的人引起的。当然,您将陷入僵局!使用PDO的beginTransaction()并将表移至InnoDB。


2
我意识到,迟到了一年,但有记录:我的故事来自一个完全由InnoDB表组成的数据库,唯一的例外是少数非规范化克隆被卡在MyISAM的泥潭中,以提供全文本索引支持。
查尔斯

Pfft,Sphinx古老而破灭ElasticSearch是新的热点。好的一天,我们实际上会将其用于旧应用程序,而不仅仅是新的应用程序…
查尔斯

PostgreSQL中的全文本搜索才是真正的赢家。太奇妙了。不需要运行其他工具/服务器即可完成其工作。不必担心保持数据同步。非常精细的控件。多个字典或自己编写。而且由于PostgreSQL自动使用多索引查询,因此您可以将其与正在运行的任何其他查询一起使用。
Brightball

2
MySQL 5.6为InnoDB表提供了全文支持。
timetofly 2014年

2

在我看来,建立持久连接会消耗更多的系统资源。也许微不足道,但仍然...


微秒级的计算机时间通常需要大量的人工时间
Andy Chase

1

使用持久性连接的解释显然是减少了相当昂贵的连接数量,尽管与其他数据库相比,使用MySQL进行连接要快得多。

持久连接的第一个麻烦是...

如果您每秒创建1000个连接,通常不保证它保持打开状态很长时间,但是Operating System可以。基于TCP / IP协议的端口不能立即回收,还必须在“ FIN”阶段投入一段时间,然后才能回收。

第二个问题...使用了大量的MySQL服务器连接。

许多人根本没有意识到您能够增加* max_connections *变量并获得与MySQL的100多个并发连接,而​​其他人则被较旧的Linux问题打败了,这些问题无法使用MySQL传递超过1024个连接。

现在允许谈论为什么在mysqli扩展中禁用了持久连接。尽管您可能滥用持久连接并获得较差的性能,但这不是主要原因。实际原因是–您可能会遇到更多问题。

在MySQL并非那么困难的情况下,在MySQL 3.22 / 3.23的所有情况下,永久连接都被放入PHP中,这意味着您可以轻松地回收连接而没有问题。但是,在更高版本中,出现了很多问题–如果您回收未提交事务的连接,则会遇到麻烦。如果您使用自定义字符集配置来回收连接,那么您将再次处于危险之中,并且可能会转换每个会话变量。

使用持久性连接的一个麻烦是它的伸缩性并没有那么好。对于连接了5000人的用户,您将需要5000个持久连接。为了消除对持久性的要求,您可以为10000个具有相似连接数量的人提供服务,因为他们可以与不共享的人共享个人连接。


0

我只是想知道部分解决方案是否是拥有一次使用连接池。您可以花一些时间在系统利用率低至最高限制时创建连接池,将它们分发出去,或者在它们完成或超时时将其杀死。在后台,您将在建立新连接时进行连接。在最坏的情况下,假设建立链接是限制因素,这应该仅与创建不带池的连接一样慢。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.