PDO 的查询与执行

Posted

技术标签:

【中文标题】PDO 的查询与执行【英文标题】:PDO's query vs execute 【发布时间】:2011-06-09 16:39:45 【问题描述】:

他们都做同样的事情,只是不同吗?

除了使用prepare之间有什么区别

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

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

?

【问题讨论】:

【参考方案1】:

query 运行标准 SQL 语句,并要求您正确转义所有数据以避免 SQL 注入和其他问题。

execute 运行准备好的语句,允许您绑定参数以避免需要转义或引用参数。如果您多次重复查询,execute 的性能也会更好。准备好的语句示例:

$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

最佳做法是坚持使用准备好的语句和execute 以提高安全性

另见:Are PDO prepared statements sufficient to prevent SQL injection?

【讨论】:

该链接导致问题的答案非常愚蠢,已经在 cmets 中受到批评。 那么,如果您在 : calories 上使用 prepare 是否等同于 mysql_real_escape_string() 来停止注射,还是您需要的不仅仅是 $sth-&gt;bindParam(':calories', $calories); 来提高安全性? 为什么query 返回一个PDOStatement,而不是像execute 这样的bool 多次重复查询doesn't work with all PDO drivers.【参考方案2】:

不,它们不一样。除了它提供的客户端转义之外,准备好的语句在服务器端编译一次,然后可以在每次执行时传递不同的参数。这意味着你可以这样做:

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

它们通常会给您带来性能改进,尽管在小范围内并不明显。 Read more on prepared statements (MySQL version).

【讨论】:

我喜欢你解释它为什么会更快的方式。 非常适合 MySQL,但 doesn't work with all PDO drivers.【参考方案3】:

Gilean's answer 很棒,但我只是想补充一点,有时最佳实践存在极少数例外情况,您可能希望以两种方式测试您的环境,看看哪种方式最有效。

在一种情况下,我发现query 对我的目的来说工作得更快,因为我正在从一个运行 php7 的 Ubuntu Linux 机器上批量传输受信任的数据,而 Microsoft ODBC driver for MS SQL Server 的支持很差。

我遇到了这个问题,因为我有一个运行很长时间的 ETL 脚本,我正试图加快速度。在我看来,query 可能比prepareexecute 更快,因为它只调用一个函数而不是两个函数,这似乎很直观。参数绑定操作提供了很好的保护,但它可能会很昂贵,如果不必要的话可以避免。

考虑到一些罕见的情况

    如果您因为it's not supported by the Microsoft ODBC driver而无法重用准备好的语句。

    如果您不担心清理输入并且可以接受简单的转义。这可能是因为binding certain datatypes isn't supported by the Microsoft ODBC driver。

    PDO::lastInsertId 不受 Microsoft ODBC 驱动程序支持。

这是我用来测试我的环境的一种方法,希望您可以复制它或在您的环境中使用更好的方法:

首先,我在 Microsoft SQL Server 中创建了一个基本表

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

现在是性能指标的基本定时测试。

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

我在我的特定环境中进行了多次不同的试验和计数,并且始终使用queryprepare/execute 快 20-30% 的结果

5.8128969669342 准备 5.8688418865204 准备 4.2948560714722查询 4.9533629417419查询 5.9051351547241 准备 4.332102060318查询 5.9672858715057 准备 5.0667371749878查询 3.8260300159454查询 4.0791549682617查询 4.3775160312653查询 3.6910600662231查询 5.2708210945129 准备 6.2671611309052 准备 7.3791449069977 准备 (7) 准备平均:6.0673267160143 (8)查询平均值:4.3276024162769

我很想看看这个测试在 MySQL 等其他环境中的对比情况。

【讨论】:

“经验证据”(或者更确切地说是人工测试)的问题是它们反映了您的(未知)特定条件并且可能对其他人有所不同,更不用说现实世界的经验证据了。然而,有些人会认为这是理所当然的,并进一步传播。 @YourCommonSense 我完全同意,并感谢您的反馈,因为我认为从我大胆的罕见情况中可以清楚地看到这一点。我假设怀疑是健康的,但忘记它并不明显。请查看我修改后的答案,并告诉我如何改进。

以上是关于PDO 的查询与执行的主要内容,如果未能解决你的问题,请参考以下文章

PDO预编译语句执行查询与DML操作

PDO / PHP / MySQL 中的性能:事务与直接执行

PDO 准备和执行查询总是返回错误

获取带有绑定参数的 PDO 查询字符串而不执行它

php pdo预处理语句与存储过程

在 PHP PDO 中获取最后执行的查询