Requête PDO vs exécution

129

Font-ils tous les deux la même chose, mais différemment?

Y a-t-il une différence en plus d'utiliser prepareentre

$sth = $db->query("SELECT * FROM table");
$result = $sth->fetchAll();

et

$sth = $db->prepare("SELECT * FROM table");
$sth->execute();
$result = $sth->fetchAll();

?

Qiao
la source

Réponses:

145

query exécute une instruction SQL standard et vous oblige à échapper correctement toutes les données pour éviter les injections SQL et d'autres problèmes.

executeexécute une instruction préparée qui vous permet de lier des paramètres pour éviter d'avoir à échapper ou à citer les paramètres. executefonctionnera également mieux si vous répétez une requête plusieurs fois. Exemple d'instructions préparées:

$sth = $dbh->prepare('SELECT name, colour, calories FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories);
$sth->bindParam(':colour', $colour);
$sth->execute();
// $calories or $color do not need to be escaped or quoted since the
//    data is separated from the query

La meilleure pratique est de s'en tenir aux déclarations préparées et executepour une sécurité accrue .

Voir aussi: Les instructions préparées PDO sont-elles suffisantes pour empêcher l'injection SQL?

Gilean
la source
Le lien mène à la question avec une réponse assez stupide, déjà critiquée dans les commentaires.
Votre bon sens le
Donc, si vous utilisez une préparation, : calories est-ce que cela équivaut mysql_real_escape_string()à arrêter les injections ou avez-vous besoin de plus que simplement $sth->bindParam(':calories', $calories);pour renforcer la sécurité?
Dan
Pourquoi queryretourne- t-il un PDOStatement , au lieu d'un booléen comme execute?
Leo
1
La répétition d'une requête plusieurs fois ne fonctionne pas avec tous les pilotes PDO .
Jeff Puckett
47

Non, ce ne sont pas les mêmes. En plus de l'échappement côté client qu'il fournit, une instruction préparée est compilée une fois côté serveur, puis peut recevoir différents paramètres à chaque exécution. Ce qui signifie que vous pouvez faire:

$sth = $db->prepare("SELECT * FROM table WHERE foo = ?");
$sth->execute(array(1));
$results = $sth->fetchAll(PDO::FETCH_ASSOC);

$sth->execute(array(2));
$results = $sth->fetchAll(PDO::FETCH_ASSOC);

Ils vous apporteront généralement une amélioration des performances, bien que cela ne soit pas perceptible à petite échelle. En savoir plus sur les instructions préparées (version MySQL) .

netcoder
la source
J'aime la façon dont vous avez expliqué pourquoi ce serait plus rapide.
timfreilly
Fabuleux avec MySQL, mais ne fonctionne pas avec tous les pilotes PDO .
Jeff Puckett
3

La réponse de Gilean est excellente, mais je voulais juste ajouter qu'il existe parfois de rares exceptions aux meilleures pratiques, et vous voudrez peut-être tester votre environnement dans les deux sens pour voir ce qui fonctionnera le mieux.

Dans un cas, j'ai trouvé que cela queryfonctionnait plus rapidement pour mes besoins car je transférais en masse des données de confiance à partir d'une machine Ubuntu Linux exécutant PHP7 avec le pilote Microsoft ODBC mal pris en charge pour MS SQL Server .

Je suis arrivé à cette question parce que j'avais un script de longue durée pour un ETL que j'essayais de presser pour la vitesse. Cela m'a semblé intuitif qui querypourrait être plus rapide que prepare& executeparce qu'il n'appelait qu'une seule fonction au lieu de deux. L'opération de liaison de paramètres offre une excellente protection, mais elle peut être coûteuse et peut-être évitée si elle n'est pas nécessaire.

Compte tenu de quelques conditions rares :

  1. Si vous ne pouvez pas réutiliser une instruction préparée car elle n'est pas prise en charge par le pilote ODBC Microsoft .

  2. Si vous ne vous inquiétez pas de la désinfection des entrées, une simple évacuation est acceptable. Cela peut être le cas car la liaison de certains types de données n'est pas prise en charge par le pilote ODBC Microsoft .

  3. PDO::lastInsertId n'est pas pris en charge par le pilote ODBC Microsoft.

Voici une méthode que j'ai utilisée pour tester mon environnement, et j'espère que vous pourrez le reproduire ou quelque chose de mieux dans le vôtre:

Pour commencer, j'ai créé une table de base dans Microsoft SQL Server

CREATE TABLE performancetest (
    sid INT IDENTITY PRIMARY KEY,
    id INT,
    val VARCHAR(100)
);

Et maintenant, un test chronométré de base pour les mesures de performance.

$logs = [];

$test = function (String $type, Int $count = 3000) use ($pdo, &$logs) {
    $start = microtime(true);
    $i = 0;
    while ($i < $count) {
        $sql = "INSERT INTO performancetest (id, val) OUTPUT INSERTED.sid VALUES ($i,'value $i')";
        if ($type === 'query') {
            $smt = $pdo->query($sql);
        } else {
            $smt = $pdo->prepare($sql);
            $smt ->execute();
        }
        $sid = $smt->fetch(PDO::FETCH_ASSOC)['sid'];
        $i++;
    }
    $total = (microtime(true) - $start);
    $logs[$type] []= $total;
    echo "$total $type\n";
};

$trials = 15;
$i = 0;
while ($i < $trials) {
    if (random_int(0,1) === 0) {
        $test('query');
    } else {
        $test('prepare');
    }
    $i++;
}

foreach ($logs as $type => $log) {
    $total = 0;
    foreach ($log as $record) {
        $total += $record;
    }
    $count = count($log);
    echo "($count) $type Average: ".$total/$count.PHP_EOL;
}

J'ai joué avec plusieurs essais et comptages différents dans mon environnement spécifique, et j'obtiens constamment des résultats 20 à 30% plus rapides avec queryque prepare/execute

5,8128969669342 préparer
5,8688418865204 préparer
4,2948560714722 requête
4,9533629417419 requête
5,9051351547241 préparer
4,332102060318 requête
5,9672858715057 préparer
5,0667371749878 requête
3,8260300159454 requête
4,0791549682617 requête
4,3775160312653 requête
3,6910600662231 requête
5,2708210945129 préparer
6,2671611309052 préparer
7,3791449069977 préparer
(7) préparer moyenne: 6,0673267160143
(8) requête moyenne: 4,3276024162769

Je suis curieux de voir comment ce test se compare dans d'autres environnements, comme MySQL.

Jeff Puckett
la source
Le problème avec les «preuves empiriques» (ou plutôt des tests artificiels) qu'ils reflètent vos conditions particulières (inconnues) et peuvent différer pour n'importe qui d'autre, sans parler des preuves empiriques du monde réel. Pourtant, certaines personnes le prendront pour acquis et feront passer le mot davantage.
Votre bon sens
@YourCommonSense Je suis tout à fait d'accord, et merci pour vos commentaires car je pensais que cela ressortait clairement de mes rares conditions audacieuses. Je suppose une bonne dose de scepticisme, mais j'oublie que ce n'est pas évident. Veuillez consulter ma réponse révisée et dites-moi comment elle peut être améliorée.
Jeff Puckett