8

这是我运行的一个简单测试,以快速了解使用 MySQL PDO 准备好的语句与使用直接查询所付出的性能损失。person 表中有 2801 行。MySQL 版本 5.5.28 和 PHP 版本 5.3.15。香草安装,无论默认参数是什么。测试在 8GB 的​​ iMac 上运行。

$pdo = new PDO('mysql:host=localhost;dbname=cwadb_local', 'root', "");
$start = microtime(true);
for ($i = 0; $i < 200; $i++) {
    $pdo->query("select * from person where name_last = 'smith' or true");
}
echo "<p>query: " . (microtime(true) - $start);

$start = microtime(true);
for ($i = 0; $i < 200; $i++) {
    $stmt = $pdo->prepare("select * from person where name_last = :last or true");
    $stmt->execute(array('last' => 'smith'));
}
echo "<p>prepare/execute: " . (microtime(true) - $start);

这是输出:

query: 21.010436058044

prepare/execute: 20.74036192894

这根本没有任何惩罚。可能性:

  • 缓存准备好的语句确实有效。(请注意,我将准备函数保留在循环中。)

  • 这是一个虚假的测试,因为它太简单了。

  • 没有理论上的理由为什么准备/执行应该更慢,而且,厌倦了不断的批评,MySQL/PDO/PHP 开发人员加倍努力使它们更快,试图让我们所有人闭嘴。

  • 其他?

这里已经多次说过,使用准备好的语句比使用查询更安全,并且使用 PDO 中的命名参数(Mysqli 没有它们),处理参数非常方便。但是,正如经常注意到的那样,如果每次执行时都必须准备语句,则会降低性能。

那么,有人可以提供一些与我的简单测试相矛盾的测试吗?或者,我们现在是否应该承认没有理由不使用准备好的语句?

4

2 回答 2

11

有一件小事要提。默认情况下,PDO 只是模拟准备好的语句。
在仿真模式下,它运行相同的旧查询,而不实际准备一条语句:)

所以,首先,

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);

打开真正的准备好的陈述。

正如人们经常注意到的那样,存在性能损失

还有一件小事要提。可悲的是,世界上真正的知识
很少。尤其是在问答网站的世界中。人们倾向于重复他们已经阅读并认为合理的信息。没有进行任何测试来证明,甚至没有亲自动手。因此,“经常注意到”根本不应该被视为可靠来源。

回到正题:虽然应该有一些惩罚,但大多数时候应该是微不足道的。如果是 - 你必须调整你的系统。

无论如何,在仿真模式下,您既“快速”又安全。

更新
好吧,在对我的数据进行测试之后,我不得不说,如果您在大型数据集上有 3 倍的差异,那么您的数据库就有问题。

对于闪电查询

select title from Board where id = 1

结果是

emulation   on      off
query      0.07    0.130
prepare    0.075   0.145

而对于相当繁琐的查询

select title from Board where id > 1

结果是

emulation   on      off
query      0.96    0.96
prepare    0.96    1.00

因此,正如我们所见,在大型数据集上,差异变得不明显。

对于闪电查询,存在一些差异,但是,因为它只需要第二个 0,0003 个派系(对于单个查询) - 我会说这是“冷漠”这个词的完美示例。

对于 query()/prepare() 之间的相同结果 - 我只有一个想法 - PDO 对所有查询使用准备/执行,即使是那些没有绑定的查询。

现在到编码问题。

是的,奇怪的 GBK 问题确实会影响 5.3.3 之前版本的 PDO。这些版本无法设置正确的编码,并且不可避免地容易受到攻击(在仿真模式下)。但是自从 5.3.3 PDO 支持在 DSN 中设置编码,现在一切都很好了。
对于 mysqli,必须mysqli_set_charset()为此目的使用相同的(难以理解的)结果。

在我自己的基于 mysqli 的类中,我使用自己的占位符实现并且根本不使用准备好的语句。不是出于性能原因,而是为了更好的可靠性。

于 2013-01-05T00:16:44.963 回答
4

我对您的方法有一些疑问:

  1. 除非在服务器上绝对没有其他东西与脚本同时运行,这是不太可能的,否则您的基本计时器会受制于 CPU 调度的突发奇想。为了解决这个问题,编写两个单独的脚本并使用 *nix 的time命令运行它们,即:time php myscript.php
  2. 由于 mySQL 缓存查询,颠倒脚本的顺序可能会生成相同的结果。
  3. 一个测试每个不做诊断。尝试运行每个脚本几百或几千次,然后平均结果以获得更全面的结果。

但是在非静态查询的情况下仍然没有理由不使用准备好的语句,除非您喜欢始终严格验证所有输入并且仍然有 SQL 注入的可能性。

于 2013-01-04T23:59:56.730 回答