如何修复消息: SQLSTATE[08004] [1040] 连接过多


How to fix Message: SQLSTATE[08004] [1040] Too many connections

我使用以下代码进行数据库连接

class Database extends PDO{
    function __construct(){
        try {
            parent::__construct(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS);
            $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'");
        } catch(PDOException $e){
            Logger::newMessage($e);
            logger::customErrorMsg();
        }
    }
}

登录等每件事,获取数据都工作正常。现在突然我收到异常错误消息

Message: SQLSTATE[08004] [1040] Too many connections
Code: 1040

如何修复此错误?

在那里有一个模型类,我正在创建新数据库。

 class Model {
protected $_db;
public function __construct(){
    //connect to PDO here.
    $this->_db = new Database();
    }
  }

我做的每一个模型,都是从模型类延伸而来的。

因为 Model 类在其构造函数中实例化一个新的 Database 对象,所以每次实例化一个Model(或任何扩展它的类)时,实际上都在打开一个新的数据库连接。 如果创建多个Model对象,则每个对象都有自己独立的数据库连接,这是不常见的,通常是不必要的,不能很好地利用资源,而且由于它已经用完了服务器的所有可用连接,因此也非常有害。

例如,循环创建Model对象数组:

// If a loop creates an array of Model objects
while ($row = $something->fetch()) {
  $models[] = new Model();
}
// each object in $models has an independent database connection
// the number of connections now in use by MySQL is now == count($models)

使用依赖注入:

解决方案是使用依赖关系注入并将Database对象传递Model::__construct()中,而不是允许它实例化自己的对象。

class Model {
  protected $_db;
  // Accept Database as a parameter
  public function __construct(Database $db) {
    // Assign the property, do not instantiate a new Database object
    $this->_db = $db;
  }
}

要使用它,控制代码(将实例化模型的代码)本身应该只调用new Database()一次。 然后,必须将控件代码创建的该对象传递给所有模型的构造函数。

// Instantiate one Database
$db = new Database();
// Pass it to models
$model = new Model($db);

对于您实际上需要为模型提供不同独立数据库连接的用例,您可以为其提供不同的数据库连接。 特别是,这对于测试很有用。可以替换测试数据库对象或模拟对象。

// Instantiate one Database
$db = new Database();
$another_db = new Database();
// Pass it to models
$model = new Model($db);
$another_model = new Model($another_db);

持久连接:

如评论中所述,使用持久连接可能是一种解决方案,但不是我推荐的解决方案。PDO 将尝试重用具有相同凭据的现有连接(就像您的所有凭据一样),但您不一定希望在脚本执行期间缓存该连接。 如果您决定这样做,则需要将属性传递给Database构造函数。

try {
  // Set ATTR_PERSISTENT in the constructor:
  parent::__construct(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS, array(PDO::ATTR_PERSISTENT => true));
  $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $this->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'");
}

相关文档在这里:http://php.net/manual/en/pdo.connections.php#example-950

单例解决方案:

使用单例模式(也不推荐),您至少可以将其简化为模型代码中的搜索/替换。 Database类需要一个静态属性来保持自身的连接。然后,模型调用Database::getInstance()而不是new Database()来检索连接。 您需要在模型代码中进行搜索和替换以替换Database::getInstance()

尽管它运行良好并且不难实现,但在您的情况下,它会使测试变得更加困难,因为您必须将整个Database类替换为同名的测试类。您不能轻松地逐个实例替换测试类。

将单例模式应用于Database

class Database extends PDO{
   // Private $connection property, static
   private static $connection;
   // Normally a singleton would necessitate a private constructor
   // but you can't make this private while the PDO 
   // base class exposes it as public
   public function __construct(){
        try {
            parent::__construct(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS);
            $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'");
        } catch(PDOException $e){
            Logger::newMessage($e);
            logger::customErrorMsg();
        }
    }
   // public getInstance() returns existing or creates new connection
   public static function getInstance() {
     // Create the connection if not already created
     if (self::$connection == null) {
        self::$connection = new self();
     } 
     // And return a reference to that connection
     return self::$connection;
   }
}

现在,您只需要更改要使用的Model代码Database::getInstance()

class Model {
    
  protected $_db;
    
   public function __construct(){
     // Retrieve the database singleton
     $this->_db = Database::getInstance();
   }
}