php - 使用 PDO 预准备语句插入时 MySQL 性能极差

标签 php mysql sql pdo insert

最近,我一直试图找出为什么特定的 PHP 脚本花费这么长时间。它是一个简单的组权限网格,从前端为每个组获取 N 个复选框,并相应地更新权限表。简单的东西。

为了最大限度地减少与数据库的往返次数,我只是截断了表并为每个权限重新插入行。这样做时,我发现插入步骤的性能绝对糟糕。一切都包含在事务中,所有查询都使用 PDO 准备好的语句。

数据库本身是MySQL,所有表都使用InnoDB引擎。插入的具体表非常简单,仅包含 3 列,如下所示:

CREATE TABLE `024_group_privs` (
  `group_priv_id` int(11) NOT NULL AUTO_INCREMENT,
  `group_id` int(11) NOT NULL,
  `activity_id` int(11) NOT NULL,
  PRIMARY KEY (`group_priv_id`),
  UNIQUE KEY `group_id` (`group_id`,`activity_id`),
  KEY `activity_id` (`activity_id`),
  CONSTRAINT `024_group_privs_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `023_groups` (`group_id`),
  CONSTRAINT `024_group_privs_ibfk_2` FOREIGN KEY (`activity_id`) REFERENCES `031_activities` (`activity_id`)
) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=latin1

如您所见,这是一个基本的多对多映射表。

有问题的 PHP 代码(带有生成后续结果的测试脚手架)如下:

<?php
try {

    //Connect to database
    $db = new PDO('mysql:host=localhost;dbname=mydatabase;charset=utf8', 'user', 'password');

    //Begin transaction.  If there are any errors; this will ensure we still have
    //  a good set of permissions when rolled back
    $db->beginTransaction();

    //Prepare insert and select statement
    $ins_perm = $db->prepare("INSERT INTO 024_group_privs (group_id, activity_id) VALUES (:group_id, :activity_id)");
    $get_act_id = $db->prepare("SELECT activity_page_key FROM 031_activities WHERE activity_id = :activity_id");

    //Nuke existing permissions list; it will be re-built shortly and is faster than
    //  trying to 'true up' the differences in existing permissions with the new ones.
    $trunc_s_time = microtime(true);
    $db->query("SET FOREIGN_KEY_CHECKS = 0");
    $db->query("TRUNCATE TABLE 024_group_privs");
    $db->query("SET FOREIGN_KEY_CHECKS = 1");
    $trunc_e_time = microtime(true);
    print("Trunc time = " . ($trunc_e_time - $trunc_s_time) . "<br/><br/>");

    $tot_ins_time = 0;
    $tot_sel_time = 0;

    //Get grout names from DB, do not trust POST object.
    $query = "SELECT group_name, group_id FROM 023_groups";
    foreach($db->query($query) as $row) {

        //Get current group and related checkbox array.  The names of these arrays are
        //  populated from the DB when the initial page is rendered.
        $group_name = $row['group_name'];
        $group_id = $row['group_id'];
        $perms = $_POST[$group_name];

        //Rebuild permissions for this group
        foreach($perms as $activity_id) {

            //Testing for select statement timing
            $sel_s_time = microtime(true);
            $get_act_id->bindValue(':activity_id', $activity_id, PDO::PARAM_INT);
            $get_act_id->execute();
            $result = $get_act_id->fetchAll(PDO::FETCH_ASSOC);
            $sel_e_time = microtime(true);
            print("Sel time = " . ($sel_e_time - $sel_s_time) . "<br/>");

            //Insert new permission <-- THIS IS THE SLOW QUERY
            $ins_s_time = microtime(true);
            $ins_perm->bindValue(':group_id', $group_id, PDO::PARAM_INT);
            $ins_perm->bindValue(':activity_id', $activity_id, PDO::PARAM_INT);
            $ins_perm->execute();
            $ins_e_time = microtime(true);

            print("Ins time = " . ($ins_e_time - $ins_s_time) . "<br/><br/>");
            $tot_ins_time += ($ins_e_time - $ins_s_time);
            $tot_sel_time += ($sel_e_time - $sel_s_time);
        }

    }
    print("<br/>Total Sel time = " . $tot_sel_time . "<br/>");
    print("Total Ins time = " . $tot_ins_time . "<br/>");
    print("Total time = " . $tot_ins_time + $tot_sel_time . "<br/>");

    //Commit changes
    $db->commit();

    //Close DB connection
    $db = null;

} catch (Exception $ex) {
    set_mysql_status($sid, "An error occurred while processing your request: " . $ex->getMessage());
    $db->rollBack();
}
?>

我的测试结果如下(为简洁起见,已缩短):

Trunc time = 2.1458051204681

Sel time = 0.00010895729064941
Ins time = 1.2832479476929

Sel time = 0.00010108947753906
Ins time = 1.2832760810852

Sel time = 0.00010204315185547
Ins time = 1.2749929428101

Sel time = 0.00011420249938965
Ins time = 5.0466451644897

Sel time = 0.00012516975402832
Ins time = 4.5408680438995

...

Total Sel time = 0.010221719741821
Total Ins time = 168.57392191887

问题:什么给出了?我希望编译后的插入语句的执行速度会比这快得多,尤其是对于如此简单的数据,更符合 select 语句的思路。数据量很小;每个表少于 100 行。没有表锁,因为这是我的私有(private)(专用)开发环境,并且盒子本身没有任何其他东西在上面运行,并且有足够的马力来执行简单的数据库事务。

我的理解/期望:由于准备好的语句和事务“包装器”,数据库往返次数应该最小化。我唯一的想法是,这是表上两个外键的结果,但考虑到数据大小与处理它的机器相比,这将是相当可悲的。也许 MySQL 会在每次插入时强制写入完整的硬盘驱动器,但这似乎从一开始就违背了事务的目的。

我已经搜索了 Google 和 SO,尽管我发现的大多数问题都集中在循环内准备查询或类似的事情上。

最后一点:此代码尚未针对安全性进行优化,旨在测试缓慢的插入。预先感谢您的任何帮助。

最佳答案

启用 native 准备好的语句:

   array(PDO::ATTR_EMULATE_PREPARES => false) 

作为 PDO 的第四个参数解决了这个问题,至于为什么它有帮助,最好在 DBA stackexchange 上询问。

关于php - 使用 PDO 预准备语句插入时 MySQL 性能极差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19573419/

相关文章:

java - 关系数据库——删除还是不删除?

php - 在 PHP 中使用 SimpleXML 创建带有命名空间的 RSS 元素

php - 通过redis订阅动态laravel channel

php - 仅选择表中最常见的值(如果重复)

c# - 自定义数据注释语法错误

mysql - 如何编写递归 MySQL 查询来连续跟踪项目管理任务记录

mysql - 慢sql查询优化,

php - 使用 jQuery 和 PHP/MySQL 表

php - 如何显示 Woocommerce 类别描述

mysql - GROUP BY 聚合和 INNER JOIN