2012-03-22 80 views
3

我听说用MySQL数据库准备好的语句可以提高速度,如果查询多次完成,我认为我在项目中有一个理想的情况。但我跑了一些基准,发现完全相反。我是否使用这些陈述错误(不是预备陈述的理想情况),还是他们没有我想象的那么快?准备好的声明没有速度优势?

这种情况是比赛结果网格。有多个学校参加多个活动,每个学校都有每个活动的分数。为了得到一个个别学校的分数的所有事件,它需要一个左一个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; 

我写了两个PHP测试脚本来对样本数据(200个事件)上运行,使用本机PDO对象(prepare()/bindValue()/​​与query()):

EDIT低于建议修改测试(香草查询需要取回,取不同的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"; 

我取了同一所学校的活动成绩(学校编号#10),并将$max设置为10,000,我得到的结果显示vanilla query快30%(25.72秒比36.79)。我做错了,还是准确的说准备好的声明即使在重复的情况下也不会更快?

编辑更新的测试现在得到33.95秒准备与34.10香草。 Huzzah,准备好的陈述更快。但是只有10,000次迭代只需几分之一秒。可能是因为我的查询不是那么复杂(Prepared语句为了他们的优势缓存分析树)?还是有更多的优化在这里呢?

+1

如何使用存储过程比较?你是否确定你有适当的索引? – 2012-03-22 15:44:57

+0

我不熟悉“存储”过程;我得看看那个。在'scores'和'events'表有相应的索引('events'具有'id'字段的主要和'scores'作为'school_id' /'event_id'一个两列PRIMARY指数) – MidnightLightning 2012-03-22 15:55:00

+0

“存储过程”很好。另一方面,“准备好的陈述”只是另一种工具。它们会产生一些开销......但在适当的情况下,它们也可以产生性能游戏,增加安全性并简化代码。这里有一个很好的链接:http://blog.ulf-wendel.de/?p=187#procon – paulsm4 2012-03-22 15:56:23

回答

-2

看来,你可能不会比较苹果和苹果。

PDO::query()执行SQL语句,返回结果集作为PDOStatement对象。

获得实际的结果,你需要遍历返回的对象,或与准备好的声明,呼吁fetchAll()装载整个结果集到一个数组

正确的香草查询循环应该可能再是:

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(); 
} 
+0

好点!移动循环之外的绑定会加快准备好的语句,并且向香草中添加提取速度会减慢它们的速度。现在我准备了33.95秒,而34.10香草。 Huzzah,准备好的陈述更快。但是只有10,000次迭代只需几分之一秒。可能是因为我的查询不那么复杂? – MidnightLightning 2012-03-22 16:51:41

+0

对于更复杂的查询,您很可能会看到更大的差异。看起来你有一个使用主键的连接,这对于数据库来说确实不难评估。更多的连接,更大的数据集以及多个索引可供选择将导致创建执行计划时产生更多开销。 – gapple 2012-03-22 22:15:33

+0

以及只有一个变量的查询。 Prepared语句只允许在每次调用execute()时将参数发送到数据库,这对于具有更多变量的较长查询具有更大的优势。 – gapple 2012-03-22 22:18:38

4

香草查询每次执行完全相同的查询,因此您只是测试“从查询缓存中获取结果”时间,而不是实际的查询执行时间。这不是一个有效的测试。

你不得不做查询建筑物内循环,所以你每次都强迫一个新的查询:

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); 
} 
+0

在Prepared Statement中,每次都绑定相同的值。你是否在说,即使相同的价值被绑定,也没有缓存? – 2012-03-22 15:48:37

+1

绑定不是一个'自由'的操作,它比反复解析整个查询稍便宜。 – 2012-03-22 15:49:42

+0

好吧,但这不是我想我问的。我从你的第一句话推断出* data *来自Vanilla中的缓存而不是准备好的声明中。但我想我误解了。 – 2012-03-22 15:56:33

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的开发仅仅是状态, “这是我们的政策总是效仿,只是曲子。”这是没有意义的给我,但不过,噢。


要使用真正准备好的语句,你必须通过一个箍跳。如果你不“T喜欢这个,怪Zend的开发者,为修复看起来像它会采取所有的5分钟(只要将身边是否)。

$pdo = new PDO($dsn, $user, $pass, array(ATTR::PDO_EMULATE_PREPARES => false)); 

我会非常感激,如果您想进行此更改后,重新运行的基准,并更新我们为这个代码的时机。

+3

“这是我们的政策,总是模仿,只是因为”。不,它不是“仅仅因为”。 2.有些人默认情况下正努力实现“真实”准备好的陈述。 – PeeHaa 2012-09-11 18:05:48

+1

你为什么会责怪“zend devs”? – 2012-09-12 00:11:38

+0

您在代码示例中混合了ATTR和PDO。 – felixfbecker 2015-04-06 17:13:56