怎么来PDO :: bindParam这么慢?

时间:2016-05-07 19:48:37

标签: php mysql pdo

已解决:第二次测试中的值引用似乎是个问题。 而是做了价值($ 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

我尝试的事情。

将引号从Double更改为Single - 无差异

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

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

这是一个很大的性能差异,特别是对于一些查询。

2 个答案:

答案 0 :(得分:2)

在代码的第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;

答案 1 :(得分:1)

问:为什么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();
    }

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

我有兴趣看到这两个(修改的)测试的时间比较。