代码之家  ›  专栏  ›  技术社区  ›  raveren

php pdo:重新准备语句如何影响性能

  •  9
  • raveren  · 技术社区  · 15 年前

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

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

    如果 ,我计划通过为每个不同的查询生成一个唯一的键来实现这一点,并将准备好的语句保存在数据库对象的私有数组中—在其唯一键下。我计划用以下方法之一获取数组键(我不喜欢其中任何一种)。按优先顺序:

    • 让程序员在调用方法时传递一个额外的、始终相同的参数——这是 basename(__FILE__, ".php") . __LINE__ (只有在循环中调用我们的方法时,此方法才有效-在大多数情况下需要此功能)
    • 让程序员传递一个完全随机的字符串(很可能是预先生成的)作为一个额外的参数
    • 使用传递的查询本身生成键-获取查询的哈希或类似的内容
    • 通过调用 debug_backtrace

    有类似的经历吗?尽管我正在为这个系统工作 应该注意优化(它相当大,而且每周都在增长),也许我什么都不担心,做我正在做的事情也没有性能优势?

    5 回复  |  直到 10 年前
        1
  •  1
  •   Inshallah    15 年前

    好吧,因为我一直在抨击为缓存设置查询键的方法,而不是简单地使用查询字符串本身,所以我做了一个简单的基准测试。下面将使用普通查询字符串与首先创建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";
    
        2
  •  6
  •   Pang Ajmal PraveeN    10 年前

    MySQL(像大多数DBMS一样)将缓存准备好的语句的执行计划,因此如果用户A为以下内容创建计划:

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

    (其中v1和v2是bind vars),然后发送要由DBMS插入的值,然后用户b发送相同的查询(但插入值不同),DBMS不必重新生成计划。也就是说,找到匹配计划的是DBMS,而不是PDO。

    但是,这意味着数据库上的每个操作至少需要2次往返(第一次是显示查询,第二次是显示绑定变量),而不是使用文字值的查询的单个往返,然后这会带来额外的网络成本。取消对查询/计划缓存的引用(和维护)也需要少量的成本。

    关键问题是,这一成本是否高于最初生成计划的成本。

    虽然(在我的经验中)使用Oracle中的预备语句显然有助于提高性能,但我不相信MySQL也是如此——但是,很大程度上取决于数据库的结构和查询的复杂性(或者更具体地说,优化器可以找到多少不同的选项来解决这个问题)。E查询)。

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

        3
  •  4
  •   Community CDub    8 年前

    相信我,在构建一个已准备好的语句缓存之前和之后,我都做过这项工作,性能提高是 非常 值得注意-请参阅此问题: 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() .

        4
  •  1
  •   Morfildur    15 年前

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

    如果要创建已准备查询的缓存,IMHO最简单的方法是MD5散列查询字符串并生成查找表。

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

        5
  •  0
  •   Daniel Alvarez Arribas    14 年前

    使用一个MD5哈希作为键,您最终可能会得到两个导致相同MD5哈希的查询。可能性不高,但可能发生。别这样。像MD5这样的有损散列算法只是一种确定两个对象是否不同的方法,但并不是一种安全的识别方法。