Mysql
 sql >> Database >  >> RDS >> Mysql

Come risolvere il messaggio:SQLSTATE[08004] [1040] Troppe connessioni

Perché il tuo Model class istanzia un nuovo Database oggetto nel suo costruttore, ogni volta che istanzia un Model (o qualsiasi classe che lo estenda), stai in effetti aprendo un nuovo connessione al database. Se crei diversi Model oggetti, ognuno ha quindi la propria connessione al database indipendente, che è rara, di solito non necessaria, non un buon uso delle risorse, ma anche attivamente dannosa in quanto ha esaurito tutte le connessioni disponibili del server.

Ad esempio, eseguire un ciclo per creare un array di Model oggetti:

// 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)

Utilizza l'inserimento delle dipendenze:

La soluzione consiste nell'usare l'inserimento delle dipendenze e passa il Database oggetto nel Model::__construct() piuttosto che consentirgli di creare un'istanza.

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;
  }
}

Per usarlo quindi, il codice di controllo (il codice che istanzia i tuoi modelli) dovrebbe esso stesso chiamare new Database() solo una volta. L'oggetto creato dal codice di controllo deve quindi essere passato ai costruttori di tutti i modelli.

// Instantiate one Database
$db = new Database();

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

Per il caso d'uso in cui hai effettivamente bisogno di una connessione al database indipendente diversa per un modello, puoi consegnarne una diversa. In particolare, questo è utile per i test . Puoi sostituire un oggetto database di prova o un oggetto fittizio.

// Instantiate one Database
$db = new Database();
$another_db = new Database();

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

Connessioni persistenti:

Come accennato nei commenti, l'utilizzo di una connessione persistente è probabilmente una soluzione, ma non è la soluzione che consiglierei . PDO tenterà di riutilizzare una connessione esistente con le stesse credenziali (come tutte le tue avranno), ma non si desidera necessariamente che la connessione venga memorizzata nella cache durante l'esecuzione dello script. Se hai deciso di farlo in questo modo, devi passare l'attributo al Database costruttore.

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'");
}

La documentazione pertinente è qui:http://php.net/manual /it/pdo.connections.php#example-950

Soluzione singleton:

Utilizzando un pattern singleton (anch'esso sconsigliato), potresti almeno ridurlo a una ricerca/sostituzione nel codice del modello. Il Database class ha bisogno di una proprietà statica per mantenere una connessione per sé. I modelli quindi chiamano Database::getInstance() invece di new Database() per recuperare la connessione. Dovresti eseguire una ricerca e sostituire nel codice del modello per sostituire Database::getInstance() .

Sebbene funzioni bene e non sia difficile da implementare, nel tuo caso renderebbe i test un po' più difficili poiché dovresti sostituire l'intero Database classe con una classe di test con lo stesso nome. Non puoi sostituire facilmente una classe di test istanza per istanza.

Applica il pattern singleton a 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;
   }
}

Ora dovresti cambiare solo il Model codice da utilizzare Database::getInstance() :

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