132

Are they both do the same thing, only differently?

Is there any difference besides using prepare between

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

and

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

?

maxhud
  • 9,261
  • 13
  • 52
  • 99
Qiao
  • 15,053
  • 27
  • 79
  • 115

3 Answers3

147

query runs a standard SQL statement and requires you to properly escape all data to avoid SQL Injections and other issues.

execute runs a prepared statement which allows you to bind parameters to avoid the need to escape or quote the parameters. execute will also perform better if you are repeating a query multiple times. Example of prepared statements:

$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

Best practice is to stick with prepared statements and execute for increased security.

See also: Are PDO prepared statements sufficient to prevent SQL injection?

Community
  • 1
  • 1
Gilean
  • 14,428
  • 10
  • 43
  • 52
  • The link leads to the question with quite stupid answer, already criticized in comments. – Your Common Sense Jan 16 '11 at 15:59
  • So if you use a prepare on the `: calories ` is that kind of the equivalent of `mysql_real_escape_string()` to stop injections or do you need more than just `$sth->bindParam(':calories', $calories);` to heighten security? – Dan Jan 05 '12 at 12:52
  • Why does `query` return a *PDOStatement*, instead of a *bool* like `execute`? – Leo Jun 12 '16 at 17:29
  • 1
    Repeating a query multiple times [doesn't work with all PDO drivers](http://stackoverflow.com/q/39088156/4233593). – Jeff Puckett Oct 07 '16 at 14:53
48

No, they're not the same. Aside from the escaping on the client-side that it provides, a prepared statement is compiled on the server-side once, and then can be passed different parameters at each execution. Which means you can do:

$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);

They generally will give you a performance improvement, although not noticeable on a small scale. Read more on prepared statements (MySQL version).

nicolae-stelian
  • 192
  • 1
  • 12
netcoder
  • 61,842
  • 17
  • 117
  • 139
3

Gilean's answer is great, but I just wanted to add that sometimes there are rare exceptions to best practices, and you might want to test your environment both ways to see what will work best.

In one case, I found that query worked faster for my purposes because I was bulk transferring trusted data from an Ubuntu Linux box running PHP7 with the poorly supported Microsoft ODBC driver for MS SQL Server.

I arrived at this question because I had a long running script for an ETL that I was trying to squeeze for speed. It seemed intuitive to me that query could be faster than prepare & execute because it was calling only one function instead of two. The parameter binding operation provides excellent protection, but it might be expensive and possibly avoided if unnecessary.

Given a couple rare conditions:

  1. If you can't reuse a prepared statement because it's not supported by the Microsoft ODBC driver.

  2. If you're not worried about sanitizing input and simple escaping is acceptable. This may be the case because binding certain datatypes isn't supported by the Microsoft ODBC driver.

  3. PDO::lastInsertId is not supported by the Microsoft ODBC driver.

Here's a method I used to test my environment, and hopefully you can replicate it or something better in yours:

To start, I've created a basic table in Microsoft SQL Server

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

And now a basic timed test for performance metrics.

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

I've played with multiple different trial and counts in my specific environment, and consistently get between 20-30% faster results with query than prepare/execute

5.8128969669342 prepare
5.8688418865204 prepare
4.2948560714722 query
4.9533629417419 query
5.9051351547241 prepare
4.332102060318 query
5.9672858715057 prepare
5.0667371749878 query
3.8260300159454 query
4.0791549682617 query
4.3775160312653 query
3.6910600662231 query
5.2708210945129 prepare
6.2671611309052 prepare
7.3791449069977 prepare
(7) prepare Average: 6.0673267160143
(8) query Average: 4.3276024162769

I'm curious to see how this test compares in other environments, like MySQL.

Community
  • 1
  • 1
Jeff Puckett
  • 28,726
  • 15
  • 96
  • 149
  • The problem with "empirical evidences" (or rather artificial tests) that they reflect your (unknown) particular conditions and may differ for anyone else, let alone the real world empirical evidence. Yet some people will take it for granted and spread the word further. – Your Common Sense Oct 08 '16 at 08:25
  • @YourCommonSense I agree completely, and thanks for your feedback because I thought that was clear from my bold rare conditions. I assume a healthy dose of skepticism, but forget it's not obvious. Please see my revised my answer, and let me know how it can be improved. – Jeff Puckett Oct 09 '16 at 03:19