【问题标题】:PHP PDO: how does re-preparing a statement affect performancePHP PDO:重新准备语句如何影响性能
【发布时间】:2011-01-09 02:31:20
【问题描述】:

我正在编写一个半简单的数据库包装类,并希望有一个可以自动操作的获取方法:它应该只在第一次准备每个不同的语句,然后绑定并执行查询连续调用。

我想主要问题是:重新准备相同的 MySql 语句如何工作,PDO 会神奇地识别语句(所以我不必)并停止操作? p>

如果不是,我打算通过为每个不同的查询生成一个唯一键并将准备好的语句保存在数据库对象的私有数组中 - 在其唯一键下。我打算通过以下方式之一获取数组键(我都不喜欢)。按优先顺序:

  • 让程序员在调用方法时传递一个额外的、始终相同的参数 - 类似于basename(__FILE__, ".php") . __LINE__ 的内容(此方法仅在我们的方法在循环中调用时才有效 - 大多数情况下都是如此需要此功能)
  • 让程序员传递一个完全随机的字符串(很可能是预先生成的)作为额外参数
  • 使用传递的查询本身来生成密钥 - 获取查询的哈希或类似的东西
  • 通过调用debug_backtrace实现与第一个项目符号(上图)相同

有没有类似经历的?虽然我为之工作的系统确实值得关注优化(它相当大并且每周都在增长),但也许我什么都不担心,做我做的事情并没有性能优势'我在做什么?

【问题讨论】:

  • 我认为将准备好的语句句柄放在一个数组中,以 SQL 为键,是唯一理智的方法。我认为您提出的其他方法没有任何好处。但是,我确实想知道 PDO 是否会自动进行这种优化...
  • 但是,如果查询很长并且被调用了数千次,那么在数组中查找这样的键将成为 IMO 自身的瓶颈。
  • 我不知道。我可能完全错了。我对 PHP 数组内部的了解不够,也从未测试过这个级别的性能。顺便说一句,在实施一种方法之前测试各种方法的性能,这是个好主意。

标签: php mysql pdo prepared-statement


【解决方案1】:

MySQL(像大多数 DBMS 一样)会缓存准备好的语句的执行计划,所以如果用户 A 创建一个计划:

SELECT * FROM some_table WHERE a_col=:v1 AND b_col=:v2

(其中 v1 和 v2 是绑定变量)然后发送要由 DBMS 插值的值,然后用户 B 发送相同的查询(但使用不同的插值值)DBMS 不必重新生成计划。即找到匹配计划的是 DBMS,而不是 PDO。

但是这意味着数据库上的每个操作都需要至少 2 次往返(第一次呈现查询,第二次呈现绑定变量),而不是使用文字值的查询的单次往返,那么这就引入了额外的网络成本。取消引用(和维护)查询/计划缓存也需要少量成本。

关键问题是这个成本是否大于首先生成计划的成本。

虽然(根据我的经验)在 Oracle 中使用准备好的语句肯定会带来性能优势,但我不相信 MySQL 也是如此 - 但是,很大程度上取决于您的数据库结构和查询的复杂性(或者更具体地说,优化器可以找到多少不同的选项来解决查询)。

尝试自己测量(提示:您可能希望将慢查询阈值设置为 0,并编写一些代码将文字值转换回匿名表示,以便写入日志的查询)。

【讨论】:

  • -1 如Caching of Prepared Statements and Stored Programs 中所述:“服务器在每个会话的基础上维护准备好的语句和存储程序的缓存。为一个会话缓存的语句不能被其他会话访问。当会话结束时,服务器会丢弃为它缓存的所有语句。”可能会出现混淆,因为准备好的语句后变量扩展以正常方式缓存,如 How the Query Cache Operates 中所述?
【解决方案2】:

相信我,我在构建准备好的语句缓存之前和之后都这样做了,性能提升非常很明显 - 请参阅这个问题:Preparing SQL Statements with PDO

这是我想出的代码,带有缓存的预处理语句:

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()

【讨论】:

  • 我很想知道您为什么决定使用 MD5 散列。我已经发布了 MD5 散列与 PHP 原生数组的简单基准测试,并且可以通过 MD5 方法找到任何改进。
  • @Inshallah:使用md5() 的决定只是为了避免在查询中出现新行 (\n)。
【解决方案3】:

好的,因为我一直在抨击为缓存键入查询的方法,而不是简单地使用查询字符串本身,所以我做了一个简单的基准测试。下面比较了使用普通查询字符串与首先创建 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";

【讨论】:

  • 感谢基准测试,遗憾的是我无法让它在这里工作,因为 $cache 从来没有任何数据并且 $q 只是“DELETEArray”,但我会在获得机会
  • @dbemerlin,是的,对不起。它现在应该可以工作了。在从 emacs 复制/粘贴后,我对代码进行了一些编辑,在此过程中引入了一些错误 :-)。
  • 我现在根据您的脚本进行了测试,并进行了这些修改:通过连接来自 $queries 的随机字符串 20 次来创建 100 个随机字符串。 foreach 这些随机字符串循环 $iter 次,每次检查字符串是否存在于 $cache 中,如果没有设置它。对于 md5:foreach 随机字符串创建哈希,然后循环 $iter 次并执行相同操作。结果:在 $iter=1 时,md5 哈希值慢 0.00008 毫秒,对于 $iter 的低值(100 和更低),没有真正的性能差异(
  • 我用不同的 $length$iter 值运行了 Instalah 的基准测试,似乎 PHP 的缓存每次都获胜。我想我会接受他的回答,因为他最终在原始问题的 cmets 中指出了最终解决方案(使用整个查询作为缓存中的键)是正确的。当我完成包装类时,我将尝试发布进一步的基准测试是否值得缓存语句(虽然不能承诺任何事情:)
  • 你们知道关联数组是作为 /hashmaps/ 实现的吗?由于生成的 md5 散列然后再次与 PH​​P 内部使用的任何内容进行散列以维护其散列图,因此只有在以下情况下使用 md5 才能提高性能:a)PHP 内部散列的性能低于公开的 md5 实现(包括所有开销调用暴露的 md5 函数,在堆上为生成的 md5 散列分配足够的内存等)和 b)md5 的输入明显大于 md5 散列 - 无论如何(因为生成的 md5 将再次散列)。
【解决方案4】:

据我所知,PDO 不会重用已经准备好的语句,因为它不会自行分析查询,因此它不知道它是否是同一个查询。

如果您想创建准备好的查询的缓存,恕我直言,最简单的方法是对查询字符串进行 md5-hash 并生成一个查找表。

OTOH:您执行了多少个查询(每分钟)?如果少于几百个,那么您只会使代码复杂化,性能提升将很小。

【讨论】:

  • 我可能遗漏了一些东西,但是为什么要创建查询字符串的 md5 呢?为什么不使用查询字符串本身
  • 因为查询字符串本身可能很长,所以查找会比较慢。哈希将允许更快的查找(当然,如果查询足够短并且查找足够少,那么它实际上可能会更慢,但在通常情况下,哈希搜索会更快)
  • 什么? MD5ing 查询字符串以获得性能?这完全是疯了:-)。我已经看到 Perl 中使用的查询字符串索引方法,效果很好。你确定你对 PHP 数组的实现方式了解足够多,才能提出这样的建议吗?
  • 可能 对于 PHP 来说是不必要的,因为我没有针对这种特定情况进行任何基准测试,但恕我直言,使用哈希值进行查找总是一个好主意,即使数组本身会散列,否则您可能会想执行类似 $key = ; 之类的操作。 foreach ($stuff as $item) { $cache[$key]->do_stuff($item);这将强制 PHP 为每个循环迭代重新哈希查询以查找键。 OTOH,我通常不太关心语言本身如何实现某些东西,所以我可能错了:你应该对其进行基准测试并找出答案。
  • 我不敢相信 PDO 不会重用已经准备好的语句 - 也许 RDMS 至少会跟踪它们。
【解决方案5】:

使用 MD5 哈希作为键,您最终可能会得到两个产生相同 MD5 哈希的查询。概率不高,但有可能发生。不要这样做。像 MD5 这样的有损散列算法只是作为一种高度确定地判断两个对象是否不同的方法,但不是识别某物的安全方法。

【讨论】:

    猜你喜欢
    • 2014-04-19
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 2013-04-21
    • 1970-01-01
    • 1970-01-01
    • 2012-06-07
    相关资源
    最近更新 更多