我应该在PHP PERFORMANCE-WISE中使用MySQL的预处理语句吗?

问题描述 投票:21回答:5

我理解MySQL中预准备语句的安全性好处。这里不需要涉及这个主题。我想知道它们的性能方面。

现在,我知道当使用预准备语句的查询在单个PHP脚本中执行两次时,它更快,因为查询只被解析一次,每次查询一次。客户端进行一次准备,然后使用二进制协议发送数据两次。二进制协议速度更快,而且您不必再次进行解析。

但是,我只想在一个PHP脚本中执行一次查询的情况呢?看起来使用准备好的声明会更糟糕,因为你要两次前往服务器,一次准备,一次发送数据。只需要解析一次的好处就丢失了,你第二次旅行就受到了惩罚。如果数据的二进制格式不够小,那么使用准备好的语句会丢失,对吧?

但是,我已经阅读了一些关于PHP的mysqli或PDO库做什么的相互矛盾的报道?它们中的任何一个是否跨脚本执行缓存预准备语句?服务器是否必须在后续页面加载时再次解析预准备语句?如果答案是否定的,那么语句不必在第二个页面加载上进行解析,那么看起来准备好的语句会更好,即使你每页面加载只执行一次查询。

请考虑MySQL版本之间是否有任何相关变化。您可以放心地假设我使用的是PHP 5.2

编辑:只是为了说清楚,我想要专门针对MySQL和PHP的答案,指定MySQL版本,如果这是不同的,并且只考虑性能,而不是易用性或安全性。

更新:我接受了我的答案,因为后续评论有一些好主意。我仍然有点失望,似乎没有人能够回答我所提出的实际问题的症结。我猜有时答案真的是“这取决于”。

php mysql performance pdo prepared-statement
5个回答
12
投票

历史

这是我的第一个Stackoverflow答案。自那以后发生了很多变化,特别是对mysql API的弃用和删除。即使你仍然使用php 5.6,也不应该使用mysql_ * api。现在PDO或mysqli是唯一可供选择的选项。由于很多原因,PDO更好。

准备好的语句是否跨页面加载缓存?

我读过一些关于PHP的mysqli或PDO库做什么的相互矛盾的报道?它们中的任何一个是否跨脚本执行缓存预准备语句?

页面加载之间不会使用相同的预准备语句。它必须每次都准备好。如果压缩每个大毫秒都很重要,那么存储过程可能是一个好主意(假设您有一个复杂的查询)。

对于大型插入(数千行),可以通过将数据转储到文本文件并使用LOAD DATA IN FILE加载来获得更大的提升。它比一系列插件快得多。

原来的答案

问题的真相是,有时mysqli更快,而在其他时候mysql api更快。但差异真的很小。如果你看一下网上的任何性能测试,差异实际上只有10到20毫秒。提高性能的最佳方法是优化表格设计。

许多测试“证明”旧的api更快更方便地忘记了为了最大的安全性,应该为查询中使用的每个变量调用mysql_real_escape_string()。

当且仅当查询中使用的所有表上的数据保持不变时,服务器才会缓存查询。

等待实际数字的另一次更新


7
投票

“使用准备好的陈述绝不是一个坏主意”

不对。如果您不知道自己在做什么,可以轻松填写​​准备好的语句缓存并超过max_prepared_stmt_count,在关闭使用预准备语句的连接之前,使应用程序无效。

准备语句专门针对业务逻辑中的紧密内部循环而设计,您将一遍又一遍地调用相同的基本查询,这是一个参数化查询的好例子,例如:

SELECT name, address, phone from tbl WHERE id = ?

并且每次通话都有不同的ID。这样,值得额外往返数据库进行准备,因为你可能要调用数百或数千次,只需更改参数即可。但是你应该从缓存中删除准备好的语句,或者在你的无状态脚本(php,perl,jsp,ruby等)结束时关闭连接。 如果您没有删除准备好的语句并且您正在使用连接池,则必然会随着时间的推移填满缓存并出现一个令人讨厌的错误“无法创建超过max_prepared_stmt_count语句”。我是根据经验说的,所以想想你是否真的需要预备语句,因为你明确知道你将在一个紧密的循环中反复重复相同的参数化查询。 如果没有,你可能正在寻找的是让mysql使用它的基本查询缓存,这是一种与预备语句列表不同的机制,其行为就像我理解的真正的LRU缓存。


1
投票

并非适用于所有情况。有时候,即使重复运行,预处理语句的执行速度也比手动构造的查询慢。

例如,当我需要在一个页面加载中插入数百行时,我发现:

$pdo -> query("INSERT INTO table (field1, field2, ...) VALUES (".$pdo -> quote($value1).", ".$pdo -> quote($value2).", ...), (row2), (row3), ..., (row100), ...");

比以下更快:

$stmt = $pdo -> prepare("INSERT INTO table (field1, field2, ...) VALUES (:value1, :value2, ...)");
foreach (/* some hundred times */) {
  $stmt -> execute(array('value1' => $value1, 'value2' => $value2, ...));
}

UPDATE

4年前我写了这个答案,从那以后我就学到了新的东西。

对于重复运行数百次的准备语句要快,您必须将所有执行包装在一个事务中。

但是,我从来没有测试哪个是更快,未准备好的单批量INSERT,包含1000行或1000个执行的已准备好的INSERT包装在一个事务中。


0
投票

使用准备好的语句绝不是一个坏主意。我最近没有在mySQL中使用过它们,但我在SQLSERVER上大量使用它们 - 它们运行得很好。

PDO库可能不执行任何缓存,但数据库将用于准备好的语句。

另外,如您所述,它们非常适合安全性。从代码中推出逻辑也非常好 - 你不会在整个应用程序中使用SQL。


0
投票

究竟是什么意思“解析第二次页面加载”?

每个执行相同脚本的PHP进程/线程/访问者不会阻止MySQL重新解析查询以准备语句,因为它在每个连接的基础上有效,因此它对在同一脚本中重复的查询非常有效。

现在,我不能争论准备+执行与简单查询,性能明智(没有重复查询);

但是,要在每个脚本执行中保存mysql以解析复杂查询,您应该使用存储过程;查询将只是CALL items(date,search_etc,id);而不是SELECT i.id FROM products as p JOIN item_prodct as t ON... JOIN items as i... ON... WHERE ... p.date ... i.search_etc ...

您可以将存储过程与预准备语句一起使用

© www.soinside.com 2019 - 2024. All rights reserved.