PHP PDO:重新准备语句如何影响性能
我正在编写一个半简单的数据库包装类,并希望有一个自动操作的获取方法:它应该只在第一次准备每个不同的语句,并在连续调用时绑定和执行查询 我想主要的问题是:重新准备相同的MySql语句是如何工作的,PDO会神奇地识别该语句(这样我就不必这么做)并停止操作吗? 如果没有,我计划为每个不同的查询生成一个唯一的键,并将准备好的语句保存在数据库对象中的私有数组中——在其唯一键下。我计划通过以下方法之一获取数组密钥(我不喜欢这些方法)。按优先顺序:PHP PDO:重新准备语句如何影响性能,php,mysql,pdo,prepared-statement,Php,Mysql,Pdo,Prepared Statement,我正在编写一个半简单的数据库包装类,并希望有一个自动操作的获取方法:它应该只在第一次准备每个不同的语句,并在连续调用时绑定和执行查询 我想主要的问题是:重新准备相同的MySql语句是如何工作的,PDO会神奇地识别该语句(这样我就不必这么做)并停止操作吗? 如果没有,我计划为每个不同的查询生成一个唯一的键,并将准备好的语句保存在数据库对象中的私有数组中——在其唯一键下。我计划通过以下方法之一获取数组密钥(我不喜欢这些方法)。按优先顺序: 让程序员在调用该方法时传递一个额外的、始终相同的参数——类
- 让程序员在调用该方法时传递一个额外的、始终相同的参数——类似于
行(只有在循环中调用我们的方法时,此方法才有效-大多数情况下都需要此功能)basename(_FILE__,“.php”)的内容__第_u
- 让程序员传递一个完全随机的字符串(很可能是预先生成的)作为额外参数
- 使用传递的查询本身生成密钥-获取查询的哈希或类似内容
- 通过调用
debug\u backtrace
有没有类似的经历?虽然我工作的系统确实值得关注优化(它相当大,而且每周都在增长),但也许我什么都不担心,而且做我正在做的事情对性能没有好处 据我所知,PDO不会重用已经准备好的语句,因为它不会自己分析查询,所以不知道它是否是同一个查询 如果要为准备好的查询创建缓存,imho最简单的方法是md5散列查询字符串并生成查找表
OTOH:您正在执行多少查询(每分钟)?如果少于几百条,那么您只会使代码复杂化,性能增益将很小。相信我,我在构建准备语句的缓存之前和之后都做过这件事。性能增益非常明显-请参见以下问题: 这是我找到的代码,带有缓存的准备语句:
function DB($query)
{
static $db = null;
static $result = array();
if (is_null($db) === true)
{
$db = new PDO('sqlite:' . $query, null, null, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING));
}
else if (is_a($db, 'PDO') === true)
{
$hash = md5($query);
if (empty($result[$hash]) === true)
{
$result[$hash] = $db->prepare($query);
}
if (is_a($result[$hash], 'PDOStatement') === true)
{
if ($result[$hash]->execute(array_slice(func_get_args(), 1)) === true)
{
if (stripos($query, 'INSERT') === 0)
{
return $db->lastInsertId();
}
else if (stripos($query, 'SELECT') === 0)
{
return $result[$hash]->fetchAll(PDO::FETCH_ASSOC);
}
else if ((stripos($query, 'UPDATE') === 0) || (stripos($query, 'DELETE') === 0))
{
return $result[$hash]->rowCount();
}
else if (stripos($query, 'REPLACE') === 0)
{
}
return true;
}
}
return false;
}
}
因为我不需要担心查询中的冲突,所以我最终使用了md5()
而不是sha1()
MySQL(与大多数DBMS一样)将缓存准备语句的执行计划,因此如果用户A为以下语句创建计划:
SELECT * FROM some_table WHERE a_col=:v1 AND b_col=:v2
(其中v1和v2是绑定变量)然后发送要由DBMS插值的值,然后用户B发送相同的查询(但插值值不同),DBMS不必重新生成计划。i、 是DBMS找到了匹配的计划,而不是PDO
然而,这意味着数据库上的每个操作都需要至少两次往返(第一次用于显示查询,第二次用于显示绑定变量),而不是一次用于具有文本值的查询的往返,因此这会带来额外的网络成本。取消引用(和维护)查询/计划缓存也会带来很小的成本
关键问题是,这一成本是否高于最初制定计划的成本
虽然(以我的经验来看)在Oracle中使用预处理语句显然会带来性能上的好处,但我不相信MySQL也是如此——不过,这在很大程度上取决于数据库的结构和查询的复杂性(或者更具体地说,优化器可以找到多少不同的选项来解析查询)
尝试自己测量它(提示:您可能希望将慢速查询阈值设置为0,并编写一些代码将文字值转换回匿名表示形式,以用于写入日志的查询).好的,因为我一直在抨击为缓存的查询设置键控的方法,而不是简单地使用查询字符串本身,所以我做了一个简单的基准测试。下面比较了使用普通查询字符串和首次创建md5哈希:
$ php -v
$ PHP 5.3.0-3 with Suhosin-Patch (cli) (built: Aug 26 2009 08:01:52)
$ ...
$ php benchmark.php
$ PHP hashing: 0.19465494155884 [microtime]
$ MD5 hashing: 0.57781004905701 [microtime]
$ 799994
守则:
<?php
error_reporting(E_ALL);
$queries = array("SELECT",
"INSERT",
"UPDATE",
"DELETE",
);
$query_length = 256;
$num_queries = 256;
$iter = 10000;
for ($i = 0; $i < $num_queries; $i++) {
$q = implode('',
array_map("chr",
array_map("rand",
array_fill(0, $query_length, ord("a")),
array_fill(0, $query_length, ord("z")))));
$queries[] = $q;
}
echo count($queries), "\n";
$cache = array();
$side_effect1 = 0;
$t = microtime(true);
for ($i = 0; $i < $iter; $i++) {
foreach ($queries as $q) {
if (!isset($cache[$q])) {
$cache[$q] = $q;
}
else {
$side_effect1++;
}
}
}
echo microtime(true) - $t, "\n";
$cache = array();
$side_effect2 = 0;
$t = microtime(true);
for ($i = 0; $i < $iter; $i++) {
foreach ($queries as $q) {
$md5 = md5($q);
if (!isset($cache[$md5])) {
$cache[$md5] = $q;
}
else {
$side_effect2++;
}
}
}
echo microtime(true) - $t, "\n";
echo $side_effect1 + $side_effect2, "\n";
使用MD5散列作为密钥,您最终可能会得到两个查询,结果是相同的MD5散列。概率不高,但可能发生。不要这样做。像MD5这样的丢失散列算法只是一种判断两个对象是否具有高度确定性的方法,但不是一种安全的识别方法。我认为将准备好的语句句柄放在数组中,使用SQL作为键,这是唯一明智的方法。我看不出您提出的其他方法有任何好处。但是,我确实想知道PDO是否会自动进行这种优化……但是,如果查询很长,并且被调用了数千次,那么在数组中查找这样的键将变得非常困难IMO本身就是一个瓶颈。我不知道。我可能完全错了。我对PHP数组内部结构了解不够,从来没有在这个级别测试过性能。顺便说一句,在实现一种方法之前测试各种方法的性能是个好主意。我可能遗漏了一些东西,但为什么要创建查询字符串的md5?为什么不使用查询字符串本身?因为查询字符串本身可能很长,所以查找速度会较慢。哈希允许更快的查找(当然,如果查询足够短,查找次数足够少,那么实际上可能会较慢,但在通常情况下,哈希搜索会更快)什么?为了性能而修改查询字符串?这太疯狂了:-)。我已经看到了Perl中使用的查询字符串索引方法,效果很好。您确定您对PHP数组的实现方式了解得足够多,可以提出这样的建议吗?对于PHP来说,这可能是不必要的,因为我还没有为这个特定的案例做过任何基准测试,但imho它始终是一个好方法