2013-10-24 30 views
0

最近,我一直在试图弄清为什么特定的PHP脚本需要这么长时间。它是一个简单的组权限网格,从每个组的前端获取N个复选框并相应更新权限表。简单的东西。使用PDO Prepared Statement进行插入时MySQL性能表现不佳

为了尽量减少到DB的往返行程,我只是简单地截断了表格并为每个权限重新插入行。做到这一点,我看到插入步骤的表现绝对糟糕。一切都包含在一个事务中,所有查询都使用PDO准备的语句。

数据库本身是MySQL,所有表都使用InnoDB引擎。被插入的特定表成为SUPER简单,只包含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 

问题:什么给?我期望编译后的insert语句的执行速度要快得多,特别是对于这样简单的数据,更多地沿着select语句的行。数据很小,每桌少于100行。没有表锁,因为这是我的私有(专用)开发环境,而且盒本身没有其他任何东西在运行,并且PLENTY可以执行简单的数据库事务。

我的了解/期望:由于准备好的语句和事务的'包装器',DB往返应该被最小化。我唯一的想法是,它是由于桌子上的两个外键产生的,但考虑到与正在处理它的机器相比的数据大小,这将是非常可悲的。也许MySQL每次插入都会强制执行完整的硬盘驱动器写入操作,但这看起来似乎会首先破坏事务的目的。

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

最后一个注意事项:此代码未针对安全性进行优化,旨在测试慢速插入。预先感谢您的帮助。

+2

据我可以看到你正在使用模拟预处理语句尝试数组(PDO :: ATTR_EMULATE_PREPARES => false)作为PDO的第四个参数,以查看性能是否提高。 –

+0

我很强烈地想要回答这个问题。为“尽量减少往返次数”,并为每组不同的表和 –

+0

无论如何,**主要问题:**如果您怀疑外键 - 为什么不在控制台中运行**单个插入查询**?没有交易,没有准备好的陈述,没有PDO。没有很长的风PHP代码? –

回答

0

启用本地准备的语句:

array(PDO::ATTR_EMULATE_PREPARES => false) 

为PDO第四个参数可以解决问题,至于为什么它有助于更​​好询问关于DBA stackexchange。

+0

这不可能是一个原因。 –

相关问题