web-dev-qa-db-fra.com

Causes de l'erreur MySQL 2014 Impossible d'exécuter des requêtes alors que d'autres requêtes non tamponnées sont actives

Mon serveur exécute CentOS 6.4 avec MySQL 5.1.69 installé en utilisant yum avec les repos de CentOS et PHP 5.4.16 installé en utilisant yum avec les repos d'ius. Edit Mis à jour vers MySQL Server version: 5.5.31 Distribué par le projet de communauté IUS, et l'erreur existe toujours. Ensuite, la bibliothèque a été changée en mysqlnd, et semble éliminer l'erreur. Néanmoins, avec ces allers-retours, vous devez savoir pourquoi cette erreur ne se manifeste que parfois.

Lorsque j'utilise PDO et que je crée l'objet PDO à l'aide de PDO::ATTR_EMULATE_PREPARES=>false, J'obtiens parfois l'erreur suivante:

Table Name - zipcodes
Error in query:
SELECT id FROM cities WHERE name=? AND states_id=?
SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
File Name: /var/www/initial_install/build_database.php
Line: 547
Time of Error: Tuesday July 2, 2013, 5:52:48 PDT

La ligne 547 est la dernière ligne de:

$stmt_check_county->execute(array($data[5],$data[4]));
if(!$county_id=$stmt_check_county->fetchColumn())
{
    $stmt_counties->execute(array($data[5]));
    $county_id=db::db()->lastInsertId();
}
//$stmt_check_county->closeCursor(); //This will fix the error
$stmt_check_city->execute(array($data[3],$data[4]));

J'ai eu un problème similaire il y a plusieurs années, mais je suis passé de PHP 5.1 à PHP 5.3 (et MySQL a probablement aussi été mis à jour)), et le problème est magiquement allé loin, et maintenant je l'ai avec PHP 5.5.

Pourquoi ne se manifeste-t-il que lorsque PDO::ATTR_EMULATE_PREPARES=>false, Et avec seulement une version alternative des PHP?

J'ai également constaté que closeCursor() corrigera également l'erreur. Cela doit-il toujours être fait après chaque SELECT requête où fetchAll() n'est pas utilisé? Notez que l'erreur se produit toujours même si la requête est quelque chose comme SELECT COUNT(col2) qui ne renvoie qu'une seule valeur.

Edit Au fait, c'est comme ça que je crée ma connexion. J'ai récemment ajouté MYSQL_ATTR_USE_BUFFERED_QUERY=>true, Mais cela ne résout pas l'erreur. En outre, le script suivant peut être utilisé tel quel pour créer l'erreur.

function sql_error($e,$sql=NULL){return('<h1>Error in query:</h1><p>'.$sql.'</p><p>'.$e->getMessage().'</p><p>File Name: '.$e->getFile().' Line: '.$e->getLine().'</p>');}

class db {
    private static $instance = NULL;
    private function __construct() {}   //Make private
    private function __clone(){}   //Make private
    public static function db() //Get instance of DB
    {
        if (!self::$instance)
        {
            //try{self::$instance = new PDO("mysql:Host=localhost;dbname=myDB;charset=utf8",'myUsername','myPassword',array(PDO::ATTR_EMULATE_PREPARES=>false,PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC));}
            try{self::$instance = new PDO("mysql:Host=localhost;dbname=myDB;charset=utf8",'myUsername','myPassword',array(PDO::ATTR_EMULATE_PREPARES=>false,PDO::MYSQL_ATTR_USE_BUFFERED_QUERY=>true,PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC));}
            //try{self::$instance = new PDO("mysql:Host=localhost;dbname=myDB;charset=utf8",'myUsername','myPassword',array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC));}
            catch(PDOException $e){echo(sql_error($e));}
        }
        return self::$instance;
    }
}

$row=array(
    'zipcodes_id'=>'55555',
    'cities_id'=>123
);
$data=array($row,$row,$row,$row);

$sql = 'CREATE TEMPORARY TABLE temp1(temp_id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (temp_id) )';
db::db()->exec($sql);

$sql='SELECT COUNT(*) AS valid FROM cities_has_zipcodes WHERE cities_id=? AND zipcodes_id=?';
$stmt1 = db::db()->prepare($sql);

$sql ='SELECT temp_id FROM temp1';
$stmt2 = db::db()->prepare($sql);

foreach($data AS $row)
{
    try
    {
        $stmt1->execute(array($row['zipcodes_id'],$row['cities_id']));
        $rs1 = $stmt1->fetch(PDO::FETCH_ASSOC);
        //$stmt1->closeCursor();
        syslog(LOG_INFO,'$rs1: '.print_r($rs1,1).' '.Rand());
        $stmt2->execute();
        $rs2 = $stmt2->fetch(PDO::FETCH_ASSOC);
        syslog(LOG_INFO,'$rs2: '.print_r($rs2,1).' '.Rand());
    }
    catch(PDOException $e){echo(sql_error($e));}            
}
echo('done');
32
user1032531

Le protocole client MySQL ne permet pas à plus d'une requête d'être "en cours". Autrement dit, vous avez exécuté une requête et vous avez récupéré certains des résultats, mais pas tous - alors vous essayez d'exécuter une deuxième requête. Si la première requête a encore des lignes à renvoyer, la deuxième requête obtient une erreur.

Les bibliothèques clientes contournent cela en récupérant toutes les lignes de la première requête implicitement lors de la première extraction, puis les récupérations suivantes itèrent simplement sur les résultats mis en cache en interne. Cela leur donne la possibilité de fermer le curseur (en ce qui concerne le serveur MySQL). Il s'agit de la "requête mise en mémoire tampon". Cela fonctionne de la même manière que l'utilisation de fetchAll (), dans le sens où les deux cas doivent allouer suffisamment de mémoire dans le client PHP pour contenir l'ensemble de résultats complet).

La différence est qu'une requête tamponnée contient le résultat dans la bibliothèque cliente MySQL, donc PHP ne peut pas accéder aux lignes tant que vous ne récupérez pas () chaque ligne séquentiellement. Tandis que fetchAll () remplit immédiatement a = PHP tableau pour tous les résultats, vous permettant d'accéder à n'importe quelle ligne aléatoire.

La principale raison pas d'utiliser fetchAll () est qu'un résultat peut être trop grand pour tenir dans votre PHP memory_limit. Mais il semble que les résultats de votre requête ne comportent qu'une seule ligne de toute façon, cela ne devrait donc pas être un problème.

Vous pouvez fermerCursor () pour "abandonner" un résultat avant d'avoir récupéré la dernière ligne. Le serveur MySQL est informé qu'il peut ignorer ce résultat côté serveur, puis vous pouvez exécuter une autre requête. Vous ne devez pas fermerCursor () tant que vous n'avez pas récupéré un jeu de résultats donné.

Aussi: je remarque que vous exécutez votre $ stmt2 encore et encore à l'intérieur de la boucle, mais il retournera le même résultat à chaque fois. Sur le principe du déplacement du code invariant de boucle hors de la boucle, vous devriez avoir exécuté ceci une fois avant de démarrer la boucle, et enregistré le résultat dans une variable PHP. Donc, indépendamment de l'utilisation de requêtes tamponnées ou fetchAll (), vous n'avez pas besoin d'imbriquer vos requêtes.

Je recommanderais donc d'écrire votre code de cette façon:

$sql ='SELECT temp_id FROM temp1';
$stmt2 = db::db()->prepare($sql);
$stmt2->execute();
$rs2 = $stmt2->fetchAll(PDO::FETCH_ASSOC);
$stmt2->closeCursor();

$sql='SELECT COUNT(*) AS valid FROM cities_has_zipcodes 
      WHERE cities_id=:cities_id AND zipcodes_id=:zipcodes_id';
$stmt1 = db::db()->prepare($sql);

foreach($data AS $row)
{
    try
    {
        $stmt1->execute($row);
        $rs1 = $stmt1->fetchAll(PDO::FETCH_ASSOC);
        $stmt1->closeCursor();
        syslog(LOG_INFO,'$rs1: '.print_r($rs1[0],1).' '.Rand());
        syslog(LOG_INFO,'$rs2: '.print_r($rs2[0],1).' '.Rand());
    }
    catch(PDOException $e){echo(sql_error($e));}            
}

Remarque J'ai également utilisé des paramètres nommés au lieu de paramètres positionnels, ce qui simplifie le passage de $ row comme tableau de valeurs de paramètres. Si les clés du tableau correspondent aux noms des paramètres, vous pouvez simplement transmettre le tableau. Dans les anciennes versions de PHP vous deviez inclure le : préfixe dans les clés du tableau, mais vous n'en avez plus besoin.

Vous devriez quand même utiliser mysqlnd. Il a plus de fonctionnalités, il est plus efficace en mémoire et sa licence est compatible avec PHP.

60
Bill Karwin

J'espère une meilleure réponse que la suivante. Bien que certaines de ces solutions puissent "résoudre" le problème, elles ne répondent pas à la question d'origine concernant les causes de cette erreur.

  1. Définissez PDO::ATTR_EMULATE_PREPARES=>true (Je ne souhaite pas le faire)
  2. Définissez PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (N'a pas fonctionné pour moi)
  3. Utilisez PDOStatement::fetchAll() (pas toujours souhaitable)
  4. Utilisez $stmt->closeCursor() après chaque $stmt->fetch() (cela a surtout fonctionné, cependant, j'ai eu plusieurs cas où cela n'a pas fonctionné)
  5. Changez PHP bibliothèque MySQL de php-mysql à php-mysqlnd (probablement ce que je ferai s'il n'y a pas de meilleure réponse)
8
user1032531

J'ai presque le même problème. Ma première requête après la connexion à db renvoie un résultat vide et supprime cette erreur. L'activation du tampon n'aide pas.

Mon code de connexion était:

try { 
    $DBH = new PDO("mysql:Host=$hostname;dbname=$db_name", $username, $password, 
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8; SET NAMES utf8",
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NUM));
} 
catch(PDOException $e) { echo $e->getMessage(); }

La solution à ma façon était de supprimer la commande initiale:

PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8; SET NAMES utf8"

Voici un code correct:

try { 
    $DBH = new PDO("mysql:Host=$hostname;dbname=$db_name", $username, $password, 
    array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NUM));
} 
catch(PDOException $e) { echo $e->getMessage(); }

Et MYSQL_ATTR_USE_BUFFERED_QUERY n'est pas forcé à true. Il est défini par défaut.

6
oleg

J'ai eu le même problème, j'envoyais des résultats à une autre fonction en milieu de boucle. La solution rapide était de sauvegarder tous les résultats dans un tableau (comme Bill l'a déclaré, si c'est trop grand, vous avez d'autres problèmes à craindre), après avoir collecté les données, j'ai exécuté une boucle distincte pour appeler la fonction une à la fois.

De plus, PDO :: MYSQL_ATTR_USE_BUFFERED_QUERY ne fonctionnait pas pour moi.

1
tand3m