【问题标题】:how come PDO::bindParam is so slow?PDO::bindParam 怎么这么慢?
【发布时间】:2016-05-07 19:48:36
【问题描述】:

已解决: 在第二次测试中引用值似乎是问题所在。 而不是做 'VALUES ($id, $t1....)' 我需要做 'VALUES ("$id", "$t1".....),在第二个测试中这确实是抛出错误。我看不到,只有在手动打印后才能看到。

嘿,我最近一直在使用 bindParam,发现我的页面加载时间显着增加。

所以我花了几个小时来诊断问题,似乎 bindParam 使用 A LOT 比旧时尚方式(直接在查询中使用参数)更多的处理时间

为了确保结果有效,我做了两次测试。

这是我的测试:

for ($j = 0; $j < 2; $j++) {
    unset($t);
    $dbh = new PDO('mysql:host=localhost;port=3306', 'root', 'root');
    $dbh->query('USE Web_Amicrom_HQ');
    $t['start'] = microtime(true);
    for ($i = 0; $i < 50; $i++) {
        $id = rand(1000000, 9999999);
        $t1 = string_random(240);
        $t2 = string_random(240);
        $t3 = string_random(1000);
        $ins = $dbh->prepare('INSERT INTO `test` (id, t1, dt, t2, t3) VALUES(:1, :2, now(), :3, :4)');
        $ins->bindParam(':1', $id, PDO::PARAM_INT);
        $ins->bindParam(':2', $t1, PDO::PARAM_STR);
        $ins->bindParam(':3', $t2, PDO::PARAM_STR);
        $ins->bindParam(':4', $t3, PDO::PARAM_STR);
        $ins->execute();
    }
    $t['loop_fact'] = microtime(true);

    echo "---- with bindParam ----\n";
    $str_result_bench = mini_bench_to($t);
    echo $str_result_bench; // string return
    echo "\n\n";
}

for ($j = 0; $j < 2; $j++) {
    unset($t);
    $dbh = new PDO('mysql:host=localhost;port=3306', 'root', 'root');
    $dbh->query('USE Web_Amicrom_HQ');
    $t['start'] = microtime(true);
    for ($i = 0; $i < 50; $i++) {
        $id = rand(1000000, 9999999);
        $t1 = string_random(240);
        $t2 = string_random(240);
        $t3 = string_random(1000);
        $ins = $dbh->prepare("INSERT INTO `test` (id, t1, dt, t2, t3) VALUES($id, $t1, now(), $t2, $t3)");
        $ins->execute();
    }
    $t['loop_fact'] = microtime(true);

    echo "---- with parameter in query ----\n";
    $str_result_bench = mini_bench_to($t);
    echo $str_result_bench; // string return
    echo "\n\n";
}

结果:

---- with bindParam ----
total time : 3136.148ms


---- with bindParam ----
total time : 2645.822ms


---- with parameter in query ----
total time : 41.693ms


---- with parameter in query ----
total time : 52.9752ms

我尝试过的事情。

将引号从双引号更改为单引号 - 没有区别

没有参数类型的bindParam(例如PDO::PARAM_INT) - 没有区别

更改了所有参数名称(例如 :1 到 :id 等) - 没有区别

这是一个很大的性能差异,尤其是对于几个查询。

【问题讨论】:

  • 您是否将其与使用execute() 与数组进行比较?
  • 也许你可以包括所有未知的功能,这样我们就可以看到我们是否得到相同的结果
  • 第二个 for 循环中的 SQL 不起作用!
  • 这是一个快速编写的基准测试的典型示例,它实际上并没有测试它的设计目的。
  • 您正在循环内准备查询。这可以在循环之外完成,因为它只需要完成一次

标签: php mysql pdo


【解决方案1】:

在代码的第 3 行中,您有以下错误 host=127,这就是为什么 bindParam 的基准测试速度太慢

$dbh = new PDO('mysql:host=127.0.0.1;port=3306', 'root', 'root');

编辑 在您的示例中,您可以避免连接到 MySQL 服务器 4 次(2 个循环,每个循环 2 次)并且不关闭连接一次。也不需要额外查询USE数据库Web_Amicrom_HQ,你可以在连接中传递那条信息。

完整代码

 $dbh = new PDO('mysql:host=localhost;dbname=Web_Amicrom_HQ', 'root', 'root');
    for ($j = 0; $j < 2; $j++) {   
            $start = microtime(true);
        for ($i = 0; $i < 50; $i++) {
            $id = rand(1000000, 9999999);
            $t1 = string_random(240);
            $t2 = string_random(240);
            $t3 = string_random(1000);
            $ins = $dbh->prepare('INSERT INTO `test` (id, t1, dt, t2, t3) VALUES(:1, :2, now(), :3, :4)');
            $ins->bindParam(':1', $id, PDO::PARAM_INT);
            $ins->bindParam(':2', $t1, PDO::PARAM_STR);
            $ins->bindParam(':3', $t2, PDO::PARAM_STR);
            $ins->bindParam(':4', $t3, PDO::PARAM_STR);
            $ins->execute();
        }
        echo "---- with bindParam ----\n";
        echo microtime(true) - $start; // time elapsed
        echo "\n\n";
    }
    for ($j = 0; $j < 2; $j++) {

        $start = microtime(true);
        for ($i = 0; $i < 50; $i++) {
            $id = rand(1000000, 9999999);
            $t1 = string_random(240);
            $t2 = string_random(240);
            $t3 = string_random(1000);
            $ins = $dbh->prepare("INSERT INTO `test` (id, t1, dt, t2, t3) VALUES($id, $t1, now(), $t2, $t3)");
            $ins->execute();
        }
        echo "---- with bindParam ----\n";
        echo microtime(true) - $start; // time elapsed
        echo "\n\n";
    }
$dbh = null;

【讨论】:

  • 在您的示例中的数据库连接中,您有两个不同的主机值。 127和127.0.0.1,第一个3300秒响应……第二个40秒,你还不觉得第一个有连接问题吗?
  • bindParam的例子中你的运行代码中是否有host=127?如果是,你能做到127.0.0.1 并把基准测试结果写给我吗?
  • 哦,我明白了。不清楚您显示的代码是错误版本还是修复版本。我把它当作前者,并没有看到127.0.0.1有什么问题。
  • 我认为如果您更正这个可能的错误输入并再次运行脚本,您会发现bindParam 更快。
  • @AndrewCheong 我没看懂,抱歉,这里有点晚了 :)
【解决方案2】:

问:为什么 PDO::bindParam 这么慢?

答:PDO::bindParam 并不慢。

问题中展示的基准测试不是 bindParam 性能的有效衡量标准。

第二个测试几乎会为每个 INSERT 抛出错误。这是因为字符串文字 (?) 没有用单引号括起来。我最初认为这些会被解释为数字。 (令人困惑的是,我们没有看到从 string_random(240) 返回的规范。我们没有看到该函数的定义,甚至没有任何返回的示例。)

如果返回一个不代表有效数字文字的 240 个字符的字符串...基准是比较插入的行 ~1500 个字符(使用 bindParam)与没有插入的行。

我的原始答案(如下)表明对这两个测试进行了修改,以便它们执行等效的功能。这将是 bindParam 与非 bindParam 的更有效比较。


原始答案

为了提高性能,在进入循环之前调用一次preparebindParam 函数。 (不必对同一个 SQL 语句多次调用 prepare。)

    $sql = 'INSERT INTO `test` (id, t1, dt, t2, t3) VALUES(:1, :2, NOW(), :3, :4)'; 
    $ins = $dbh->prepare($sql);
    $id = 0;
    $t1 = 0;
    $t2 = 0;
    $t3 = 0;
    $ins->bindParam(':1', $id, PDO::PARAM_INT);
    $ins->bindParam(':2', $t1, PDO::PARAM_STR);
    $ins->bindParam(':3', $t2, PDO::PARAM_STR);
    $ins->bindParam(':4', $t3, PDO::PARAM_STR);
    for ($i = 0; $i < 50; $i++) {
        $id = rand(1000000, 9999999);
        $t1 = string_random(240);
        $t2 = string_random(240);
        $t3 = string_random(1000);
        $ins->execute();
    }

如果我们使用bindValue 而不是bindParam,我们需要在每次执行前立即执行bindValue


对于第二个测试(不使用 bindParam),与第一个测试不同,该代码易受 SQL 注入攻击。为了使第二个测试与第一个测试等效,我们需要确保正确转义这些值,以便它们可以安全地包含在 SQL 文本中。

    for ($i = 0; $i < 50; $i++) {
        $id = rand(1000000, 9999999);
        $t1 = string_random(240);
        $t2 = string_random(240);
        $t3 = string_random(1000);
        $sql = "INSERT INTO `test` (id, t1, dt, t2, t3) VALUES ( " 
             . $dbh->quote( $id ) . ", "
             . $dbh->quote( $t1 ) . ", "
             . "NOW(), "
             . $dbh->quote( $t2 ) . ", "
             . $dbh->quote( $t3 ) . ")";
        $ins = $dbh->prepare($sql);
        $ins->execute();
    }

请注意,quote 函数不仅仅是在值周围加上引号。它还“转义”文本中包含的单引号,以及 MySQL 将解释的其他字符。 (在最初的第二个测试中,我们没有看到值周围的单引号,所以看起来 MySQL 会在数字上下文中评估这些值。我们预计很多这些行在 t1 中会有“0”, t2 和 t3 列。

我很想看看这两个(修改后的)测试的时间比较。

【讨论】:

  • 1.这对他的设置毫无帮助。 2. 它无法解释如此巨大的差异。
  • 这使您的回答成为题外话。
  • @YourCommonSense: 1) 我建议的修改 do 通过消除其他差异来帮助设置对 bindParam 性能影响的测量。 2)如果其中一个循环正在插入行,而另一个测试抛出语法错误,它确实解释了“如此巨大的差异”。您可能认为 OP 比较是衡量“bindParam”性能的有效方法。你有权发表你的意见。您可以随意对我的回答投反对票。到目前为止只有一个反对票,所以请随意添加您的。
  • 您的答案唯一解释的是为什么非 bindParam 版本可能会很慢。而 OP 的经历则完全相反。
  • @YourCommonSense:恰恰相反。 bindParam 版本可以插入 1500 个字符的行。非 bindParam 版本在语法检查期间发出插入语句,这些语句在语句处理的早期就失败了。非 bindParam 做的工作少了很多。它不执行语义检查、制定执行计划、获取锁、检查约束、访问执行块拆分操作的块、生成回滚、将更改写入二进制日志/innodb 日志、释放锁。我们预计 OP 非 bindParam 版本会更快
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-13
  • 2014-07-28
  • 2014-05-27
  • 1970-01-01
  • 2012-02-12
  • 2012-09-05
相关资源
最近更新 更多