【问题标题】:PDO slow execute() phase when using placeholders使用占位符时 PDO 缓慢执行()阶段
【发布时间】:2019-07-24 12:31:23
【问题描述】:

我正在制作一些返回计数和按结果分组的 JSON 端点。对于这个端点,我使用 PDO 和命名占位符。使用命名占位符时,PHP 响应在execute 阶段最多需要六秒钟。当执行相同的查询并将值直接放入我的查询中时,响应几乎是瞬间的。

我正在更新仍然使用 mysql_query() 来使用 PDO 语句的旧代码。

    <?php
    header("Access-Control-Allow-Origin: *");
    header("Content-Type: application/json; charset=UTF-8");
    header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
    $conn = new PDO('mysql:host=localhost;dbname=nameOfDB', 'username', 'password');
    $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $conn->exec("set names utf8");

    $query_slow = "SELECT t.meta as meta, count(*) as cnt 
    FROM field, repo, t, uplink, link  
    WHERE (
        repo.rp1 = field.id OR 
        repo.rp2 = field.id OR 
        repo.rp3 = field.id OR 
        repo.rp4 = field.id)
    AND repo.combination = t.meta 
    AND t.doc_id = uplink.doc_id 
    AND uplink.written = 1 
    AND uplink.link_id = link.id 
    AND field.id = :field 
    AND t.earliest > :min 
    AND t.latest < :max 
    GROUP BY t.meta 
    ORDER BY cnt desc";

    if($parameters){
        $stmt = $conn->prepare($query_slow);
        $stmt->execute($parameters);
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        echo json_encode($data, JSON_UNESCAPED_UNICODE );
    }

如果我执行var_dump($parameters),则会返回以下内容:

    $parameters = array(3) {
      [":field"]=>
      int(5)
      [":min"]=>
      int(-1000)
      [":max"]=>
      int(600)
    }

这里是快速查询:

    $query_fast = "SELECT t.meta as meta, count(*) as cnt 
    FROM field, repo, t, uplink, link  
    WHERE (
        repo.rp1 = field.id OR 
        repo.rp2 = field.id OR 
        repo.rp3 = field.id OR 
        repo.rp4 = field.id)
    AND repo.combination = t.meta 
    AND t.doc_id = uplink.doc_id 
    AND uplink.written = 1 
    AND uplink.link_id = link.id 
    AND field.id=5 
    and t.earliest > -1000 
    and t.latest <600  
    GROUP BY t.meta 
    ORDER BY cnt desc";

    if(1==1){
    // I ditched the $parameters, so my execute is empty.
    $stmt = $conn->prepare($query_slow);
    $stmt->execute();
    $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
    echo json_encode($data, JSON_UNESCAPED_UNICODE );
    }


slow_query 最多可能需要 6 秒才能运行。如果我运行相同的查询并填写值和一个空的execute(),我的响应几乎是即时的。我对 PDO 做错了什么?

execute() 提供数组时,我的页面响应缓慢。使用没有参数的查询时。响应是即时的。

@RiggsFolly 要求的我的计时码:

所以我在那里运行了计时代码:

    if($parameters){
    $time_prepare = microtime(true);
    $stmt = $conn->prepare($querygraph);
    $time_pre_exec = microtime(true);
    $stmt->execute(($parameters));
    //$stmt->execute();
    $time_post_exec = microtime(true);
    $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $time_post_fetch = microtime(true);
    echo json_encode($data, JSON_UNESCAPED_UNICODE );
    }

这是使用参数 5、-800 和 800(之前使用的相同)。我的计时码显示:

start:          1563972660.9188      (First line of the script)
prepare:        1563972660.9197        ==$time_prepare
pre execution:  1563972660.9201  ==$time_pre_exec
post execution: 1563972669.0058  ==$time_post_exec ==> 9 seconds!
post fetch:     1563972669.0058 

在没有占位符的查询上使用相同的参数时,我使用相同的查询和一个空的 execute()。我用我在准备好的语句中使用的相同值替换了占位符。时序如下:

prepare:        1563973120.2965
pre execution:  1563973120.2969      //before execution()
post execution: 1563973120.312      //after executeion()
post fetch:     1563973120.3121

【问题讨论】:

  • 我使用$db-&gt;setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND,'SET NAMES UTF8');可能对速度没有帮助
  • 为什么不用JOIN 来代替?
  • 我在这里没有看到任何计时码。如果您添加了一些,这将有助于确定时间消耗在哪里
  • 尝试使用适当的绑定类型分别绑定变量。我怀疑这会有所帮助,但有一些谣言
  • @Qirel;将查询结构更改为JOIN 将如何改进execute()phase。如果我在 PHPmyadmin 中执行此查询或使用填充的值而不是占位符执行相同的查询,则速度非常好。

标签: php mysql pdo


【解决方案1】:

对于那些有类似问题的人; @YourCommonSense 的建议解决了这个问题。

我必须明确使用 bindValue();现在整个执行 - 从开始到结束 - 大约需要 0.02 秒,这比我希望的要好得多。对于动态生成的查询,我将代码更改为:

$conditions = [];           //values to be inserted by type on placeholder
$parameters = [];           //kind of thing to execute in the query with the placeholder (substring of a query with a named placeholder)
$bindTypes=[];              //the type expected to be for a placeholder (the function takes this as third argument. It should be a long type (strings give errors, hence the if/else if block))
$placeholders=[];           //contains only the :placeholders. Use this to tell the bindvalue() function that you want to bind a value X to placeholder :X 

if(isset($_GET["min"])){
    $mindate = (int)$_GET["min"];
    if($mindate != ""){
        $conditions[] = 't.earliest>= :mindate';
        $parameters[] = $mindate;
        $bindTypes[] = 'int';
        $placeholders[] = ':mindate';
    }
}
if(isset($_GET["max"])){
    $maxdate= (int)$_GET["max"];
    if($maxdate!= ""){
        $conditions[] = 't.latest>= :maxdate';
        $parameters[] = $maxdate;
        $bindTypes[] = 'int';
        $placeholders[] = ':maxdate';
    }
}

if($parameters){
    $stmt = $conn->prepare($querygraph);
    for ($i=0; $i<count($placeholders); $i++) {

        if($bindTypes[$i]=='str'){// it's better to use integers as comparison 
            $stmt->bindValue($placeholders[$i], $parameters[$i], PDO::PARAM_STR);
        } else if($bindTypes[$i]=='int'){
            echo $placeholders[$i];
            $stmt->bindValue($placeholders[$i], $parameters[$i], PDO::PARAM_INT);
        }

    } 
    $stmt->execute();
    $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
    echo json_encode($data, JSON_UNESCAPED_UNICODE );
}

【讨论】:

  • 我打算向你推荐这个,因为 MySQL 在转换类型方面可能真的很糟糕。过去我遇到过一个问题,即删除数字字符串周围的引号会产生巨大的影响,但就我而言,我有数百个字段。在您的情况下,我认为优化器无法理解数字字符串并且没有正确优化查询。最好让 MySQL EXPLAIN 执行。此外,您应该真正考虑 @Qirel 告诉您的有关 JOIN 的内容,它可以在某些边缘情况下帮助优化器。
  • @Dharman;有趣的是,即使在我的代码中使用(int)$somevar;查询仍然很慢。我现在了解到这与标准 PDO 行为有关:github.com/vlucas/spot/issues/4。我只编程了 2 年;但这是我第一次看到这种行为。绝对要牢记。
【解决方案2】:

绑定值并不会真正影响性能(准备好的语句确实会两次往返数据库,但性能差异确实可以忽略不计)。我宁愿建议您改用 JOIN 查询 - 这也将简化您的 WHERE 子句,可能会影响性能。

您也可以直接指定连接对象的字符集,而不必理会SET NAMES 查询。

如果查询很慢,您应该查看表的索引 - 您是否正确使用了表上的任何主键?如果是,您仍然可以向您加入的字段添加一些索引。例如,field 表的 ID 和 uplink 表的 doc_id 都应该是主键。

<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

$conn = new PDO('mysql:host=localhost;dbname=nameOfDB;charset=utf8', 'username', 'password');
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$query = "SELECT t.meta, COUNT(*) as cnt 
          FROM field AS f 
          JOIN rep AS r
              ON f.id IN (r.rp1, r.rp2, r.rp3, r.rp4)
          JOIN t 
              ON t.meta = r.combination
          JOIN uplink AS u 
              ON u.doc_id = t.doc_id
          JOIN link AS l 
              ON l.id = u.link_id
          WHERE u.written = 1
            AND f.id = :field 
            AND t.earliest > :min 
            AND t.latest < :max 
          GROUP BY t.meta 
          ORDER BY cnt DESC 
          ");

if ($parameters) {
    $stmt = $conn->prepare($query);
    $stmt->execute($parameters);
    $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
}

【讨论】:

  • 我用占位符运行连接查询,时间是:开始:1563975139.986 准备:1563975139.9869 执行前:1563975139.9894 执行后:1563975148.3492 获取后:1563975148.3492 执行相同的查询时,没有占位符,开始:1563975287.6505 准备:1563975287.6512 执行前:1563975287.6537 执行后:1563975287.6831 获取后:1563975287.6831 我不认为查询是罪魁祸首,这与execute()部分有关
  • @Clueless_captain 你不能在你的时间上加上一个简单的减法(round($start-$end))来得到时间吗?相信我,计算机在数学方面比人类好得多
  • @YourCommonSense;确保这些是四舍五入的值:这是用于使用占位符运行的。我不得不在点后面使用带有 2 个数字的浮点数,因为查询本身实际上是那么快。:开始:0 准备:0 执行前:0 执行后:8.17 获取后:8.17 这些是相同查询的值,占位符直接填写。我使用了相同的值:开始:0 准备:0 执行前:0 执行后:0.02 获取后:0.02 我检查了 JSON 输出,它返回了相同的数据。
  • @Clueless_captain 你可能会错过我在你的问题下发布的建议。如果您没有其他想法,可以尝试以防万一
  • @YourCommonSense:你太棒了!! (我错过了最初的评论。它需要一些修补才能让它处理动态生成的查询。但现在它可以工作了。我不敢相信没有人教过我bindValues() 进行动态查询。非常感谢,你成为救星!(如何让您的评论成为答案,以便其他人可以找到它?)
猜你喜欢
  • 2011-11-01
  • 2013-02-10
  • 1970-01-01
  • 2010-10-26
  • 2021-08-24
  • 2012-04-20
相关资源
最近更新 更多