如何在不增加最大连接数的情况下处理PHP中过多的数据库连接?

4

有一个名为function.php的文件,我使用require_once将其调用到网站的所有页面中。这个文件包含了超过一百个函数。

当有太多访问者或搜索引擎爬虫快速爬取时,我会从mysqli_connect()得到max_user_connections错误,紧接着会出现以下错误:

mysqli_query() expects parameter 1 to be mysqli, boolean given

函数文件的代码如下:

<?php 
function db_connect(){
    $link=mysqli_connect("host","dbuser","dbpass","dbname");
    if (mysqli_connect_errno()) {
      echo "Failed to connect to MySQL: " . mysqli_connect_error();
    }
    mysqli_query($link,"set names 'utf8'");
}

db_connect();

function func_1(){
    $result=mysqli_query($link,"select * from ...");
    ...
}

function func_2(){
    $result=mysqli_query($link,"select * from ...");
    ...
}

.
.
.

function func_100(){
    $result=mysqli_query($link,"select * from ...");
    ...
}
?>

最后,我关闭了数据库连接。由于我已经检查过我的数据库每个用户的最大连接数是50,而且主机提供商不会改变它(他们说这已经足够了)。

那么,一般来说我该如何解决这个问题?(使用简单的PHP或面向对象编程)


我不知道哪里出了问题 - 我怀疑错误可能在你没有展示给我们的代码的某个部分 - 但是你的 db_connect 函数正在将连接分配给一个局部变量,然后从未传递它出去。这会在某个地方让你遇到麻烦! - Ben Hillier
对不起,我忘了。在代码中,我正在使用" global $link "并需要它。它通常是正常工作的,但有时会出现此错误。 - Rasoul
7个回答

1

显然,在处理每个网页请求的过程中,您正在打开多个与MySQL服务器的连接。

在代码文件中定义db_connect()函数之后,立即调用该函数。这可能导致它被调用的次数比必要的多。每次调用它都会消耗连接池中的稀缺资源。

相反,要确保每个页面的处理代码最多调用一次该函数,请进行一些工作。最好将MySQL连接作为处理逻辑的一部分来管理,而不是依赖于require_once来完成。使用完连接后,请确保关闭打开的连接。

当您确定每个正在处理的页面最多只有一个MySQL连接打开时,那么您可能需要减少Web服务器可以同时处理的php文档数量。这可以通过调整apache Web服务器参数MaxClients和/或MaxRequestsPerChild来完成。另一个流行的Web服务器nginx也有类似的参数。减少此容量不会损害用户,因为Web服务器从队列中处理页面请求。


他们可以使用类似 if (!$link) 的方式来避免创建多个连接,但是如果他们真的只使用 require_once,那就不是问题。但这是更好编码的一个好点子,我会在我的回复中加上。 - Sakura Kinomoto

0

这是我在处理偶尔高流量时发现的解决方案。关键是检测到MySQL客户端出现此错误并等待重试连接。

它不会像减少所需连接数或增加连接一样有效,但在紧急情况下,它应该可以防止错误发生,假设连接实际上最终被关闭。它会减慢访问者的体验(加载时间会受到影响),但我认为让用户稍微等待一段时间比出现错误更好。

$sql = @new mysqli($db_host, $db_user, $db_pass, $db_name);
$iSqlAttempt = 1;
//Detect the maximum connection error.
while ($sql->connect_errno == 1040 && $iSqlAttempt < 10) {
  //Sleep 10ms then retry. The time doubles after each attempt.
  usleep(pow(2, $iSqlAttempt)*10000);
  $sql = @new mysqli($db_host, $db_user, $db_pass, $db_name);
  $iSqlAttempt ++;
}
if ($sql->connect_error) {
  die('Connect Error ('.$sql->connect_errno.') '.$sql->connect_error);
}

如果在10次尝试/10秒后仍无法访问数据库,则会退回报告错误,但希望您的情况不会那么糟糕。

希望这有所帮助!如果有任何问题,请告诉我。


0
我会添加一个方法getLink,在所有查询调用中使用它:
function getLink() {
    static $link;

    if(! $link) 
        $link = db_onnection() ;

    return $link;
} 

然后在您的查询中:

mysqli_query(getLink(), "SELECT id,...");

此外,这是单例模式的一种变体,而单例模式是一种反模式!


0
如果连接数量因为无效原因太多,那么就要修复它。查看连接何时关闭以及如何关闭。查看连接的持久性。如果即使确保这些都没问题,你仍然有太多的连接,那么你可能需要在支持它的环境中开发一个多线程独立应用程序,并在那里使用大约10个持久连接的线程。该应用程序将使用队列来知道需要从数据库请求什么以及响应应该发送到哪里,每当工作线程完成处理后,获取其结果,将其发送给请求者并继续处理队列中的下一项。

0

我直言不讳地说,由于以下原因,这里没有编码解决方案

我已经检查过我的数据库每个用户的最大连接数为50,主机提供商不会更改它(他们说这已经足够了)。

我怀疑你正在使用一些廉价/免费的托管解决方案,他们可以保持廉价/免费是因为他们限制了你的带宽。对于个人的WordPress网站来说,每天1000次访问量的中等流量,50个连接是足够的。但很容易超出这个限制。PHP正在创建超过50个并发连接。这是一个资源问题,您只能通过增加可用连接数来解决它。

由于您当前的主机无法做到这一点,您需要转移到可以做到这一点的主机上。否则,您必须接受一些访问者无法获得他们期望的网页的事实。


你说得没错,但这个主机不是很便宜(对我来说不是!!)。我只是在寻找当前情况下的最佳解决方案。 - Rasoul
顺便提一下,你可以很容易地在50个数据库连接的限制内,在5分钟内拥有10,000个连接和9,999个断开连接。 - Zodzie

0

使用 单例模式,可以将其应用于仅$link或所有function.php


-1

你有多种选择:

首先,你正在使用超出作用域的$link变量。你可以用以下代码来解决它:

<?php 
$link = null;
function db_connect(){
    global $link;
    $link=mysqli_connect("host","dbuser","dbpass","dbname");
    if (mysqli_connect_errno()) {
      echo "Failed to connect to MySQL: " . mysqli_connect_error();
    }
    mysqli_query($link,"set names 'utf8'");
}

db_connect();

function func_1(){
    global $link;
    $result=mysqli_query($link,"select * from ...");
    ...
}

function func_2(){
    global $link;
    $result=mysqli_query($link,"select * from ...");
    ...
}

.
.
.

function func_100(){
    global $link;
    $result=mysqli_query($link,"select * from ...");
    ...
}
?>

第二点:(如O.Jones所建议的)。您只依赖于require_once。您可以在创建连接之前检查连接是否已建立,方法如下:
if (!$link) db_connect();

针对该情况,在一个总是被调用的文件中获取已声明的$link变量。

第三点:PHP自动管理连接并在向客户端服务最后一个字节后销毁它们。也许,如果你在不需要连接时使用mysqli_close函数手动关闭连接,可以解决问题。你可以在这里查看如何使用该函数的文档:http://php.net/manual/zh/mysqli.close.php

如果问题仍然存在,也许你需要使用持久连接,以减少从PHP到数据库的连接数。你可以在主机名前加上 "p:"(不带引号)来实现这一点。例如,要连接到sql.myserver.com,你需要使用:

$link=mysqli_connect("p:sql.myserver.com","dbuser","dbpass","dbname");

您可以在 PHP 手册的此页面 http://php.net/manual/zh/mysqli.persistconns.php 中获取有关 mysqli 持久连接的更多信息。


global是一个糟糕的解决方案 - Machavity
为什么是-1?也许全局变量不是最好的选择,但至少可以在不重写所有代码的情况下解决问题。如果您有更好的选择,请公开它。我可以从中学习。但是如果批评带有轻蔑和非建设性,我将无法再学习或解决这个问题。 - Sakura Kinomoto
因为你正在传播一种糟糕的编码方式。如果有人在其他地方使用$link,你的代码会突然停止工作。你还建议了另一件不好的事情:持久连接。从经验上讲,持久连接在终止时不会被释放,这对网站来说是一件坏事。通过这样做,你实际上会加剧问题,因为PHP仍然会在持久连接之上创建新的连接。 - Machavity

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接