我听说如果查询多次完成,使用MySQL数据库的Prepared Statements可以提高速度,我认为我在项目中有一个理想的情况。但是我运行了一些基准测试,结果恰恰相反。我使用这些陈述是错误的(不是准备陈述的理想情况),还是它们没有我想象的那么快?
情况是锦标赛结果网格。有多所学校参加了多个活动,每所学校都有每个活动的分数。为了获得所有事件的单个学校的分数,它需要一个带有LEFT JOIN的SQL查询,如:
SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`=:school_id;
我使用原生PDO对象(prepare()
/ bindValue()
/ execute()
与query()
)编写了两个用于针对示例数据(200个事件)运行的PHP测试脚本:
编辑使用以下建议修改测试(vanilla查询需要获取,获取不同的ID,并在循环外绑定准备)。现在只为准备好的陈述提供适度的速度优势:
准备好的声明:
$start = microtime(true);
$sql = 'SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`=:school_id';
echo $sql."<br />\n";
$stmt = $db->prepare($sql);
$sid = 0;
$stmt->bindParam(':school_id', $sid);
for ($i=0; $i<$max; $i++) {
$sid = rand(1,499);
$stmt->execute();
$rs = $stmt->fetchAll();
}
$delta = bcsub(microtime(true), $start, 4);
echo "<strong>Overall time:</strong> $delta<br />\n";
echo "<strong>Average time:</strong> ".($delta/$max)."<br />\n";
香草查询:
set_time_limit(15); // Add time for each run
$start = microtime(true);
$sql = 'SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`={$sid}';
echo $sql."<br />\n";
for ($i=0; $i<$max; $i++) {
$sid = rand(1,499);
$stmt = $db->query("SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`={$sid}");
$rs = $stmt->fetchAll();
}
$delta = bcsub(microtime(true), $start, 4);
echo "<strong>Overall time:</strong> $delta<br />\n";
echo "<strong>Average time:</strong> ".($delta/$max)."<br />\n";
我一遍又一遍地获取相同学校的活动分数(学校ID#10),并将$max
设置为10,000,我得到的结果显示香草查询快30%(25.72秒对36.79) 。我是做错了,还是这样准确,即使在重复的情况下,准备好的陈述也不会更快?
编辑更新后的测试现在准备33.95秒而不是34.10香草。 Huzzah,准备好的陈述更快。但是10,000次迭代只需要几分之一秒。可能是因为我的查询不是那么复杂(Prepared语句缓存解析树的优势)?或者还有更多优化吗?
答案 0 :(得分:4)
vanilla查询每次执行完全相同的查询,因此您只是测试“从查询缓存中获取结果”次数,而不是实际的查询执行时间。这不是一个有效的测试。
您必须在循环中进行查询构建,因此每次都要强制执行新查询:
for ($i=0; $i<$max; $i++) {
$sql = <<<EOL
SELECT e.id, e.name, c.competing, c.raw, c.final
FROM events e
LEFT JOIN scores c ON e.id=c.event_id
WHERE c.school_id= $i;
EOL;
$rs = $db->query($sql);
}
答案 1 :(得分:1)
这些人中没有一个明显地指出:
PDO 默认使用MySQL 模拟准备好的语句,即使驱动程序支持它们。
这是PHP PDO manual page所说的完全相反,但是view the source code and you'll see they always, de facto, use emulated prepared statements(我认为这是一个编码错误,但当我提交报告时,它是被归类为WONT_FIX并且我有一些Zend dev仅仅声明,“我们的政策始终是模仿,只是因为。”这对我来说没有意义,但是很好。
要使用真正准备好的陈述,你必须跳过一个箍。如果你不喜欢这个,请责怪Zend开发者,因为修复看起来需要5分钟(只需移动一下)。
$pdo = new PDO($dsn, $user, $pass, array(ATTR::PDO_EMULATE_PREPARES => false));
如果您进行此更改,重新运行基准测试,并使用此代码更新我们的时间安排,我将非常感激。
答案 2 :(得分:-2)
看来你可能没有比较苹果和苹果。
PDO::query()
执行SQL语句,将结果集作为PDOStatement
对象返回。
要获得实际结果,您需要迭代返回的对象,或者与准备好的语句一样,调用fetchAll()
将整个结果集加载到数组中
正确的vanilla查询循环应该是:
for ($i=0; $i<$max; $i++) {
$stmt = $db->query($sql);
$rs = $stmt->fetchAll();
}
或者从准备好的语句循环中删除fetchAll()
调用。
您还可以使用bindParam()
代替bindValue()
$school_id = null;
$stmt->bindParam(':school_id', $school_id);
for ($i=0; $i<$max; $i++) {
$school_id = 10;
$stmt->execute();
$rs = $stmt->fetchAll();
}