优化包含 PDO 查询的 while 循环

Posted

技术标签:

【中文标题】优化包含 PDO 查询的 while 循环【英文标题】:Optimizing a while loop that contains PDO query's 【发布时间】:2013-10-08 15:16:18 【问题描述】:

信息

目前正在构建一个通知页面,其中列出了所有登录的用户通知,其中包含有关每个通知的信息。

例如,没有信息

You have an unread message

有信息

<Sarah> Sent you an message

问题

因为通知需要用户名(用于消息通知)或文章标题(比如您关注作者并且他们发布了新博客文章)等数据,所以一个通知需要从用户表中提取用户名,然后还需要标题来自博客表的博客)这会导致我的页面甚至在本地主机上滞后,我猜一旦上传并在野外测试会变得更糟。

当前代码

function showNotifications($userid)
    $STH = $this->database->prepare('SELECT * FROM notifications WHERE user_id = :userid ORDER BY timestamp DESC');
    $STH->execute(array(':userid' => $userid));
    while($row = $STH->fetch(PDO::FETCH_ASSOC))
        $this->sortNotif($row);

关于下面函数的快速解释,因为我有不同类型的通知我为特定类型创建了一堆 ID,例如 type 1 = new message,type 2 = new blog post

function sortNotif($notif)

    switch ($notif['type']) 

        case "1":
            $msg = $this->getMessageData($notif['feature_id']);
            $user = $this->userData($msg['sender']);
            echo '<li><i>'.timeAgo($notif['timestamp']).'</i><a href="user.php?username='.$user['username'].'">'.$user['first_name'].'</a> sent you a <a href="inbox.php?message='.$msg['id'].'">message</a></li>';
            break;

    


正如您所看到的,仅显示用户有一条新消息它会创建 2 个查询,并且一旦循环通过 40 个左右的通知,超过 100 个左右的用户就会对服务器造成压力。

结束语

如果有人需要更多信息,请询问,我一定会尽快更新此问题,谢谢!

编辑

以下是以下 cmets 中要求的表结构。

通知

id | user_id | feature_id | type | timestamp | read

用户

id | username | password | first_name | last_name | email | verify_hash | avatar | type 

消息

id | receiver | sender | replying_to | deleted | body | timestamp | read

【问题讨论】:

你能从你的 sortNotif 函数中的 switch/case 语句中取出一些通用代码吗?您是否使用 mysql EXPLAIN 或其他工具分析了您的查询?那里有优化的余地吗? 我用的是网上找的一个小php sn-p,多次测试了多个页面。 notification.php 页面采用“页面生成时间为 0.6599 秒”。而所有其他“页面在 0.0467 秒内生成”。此外,页面实际上明显变慢了。如果您愿意,如果您有什么建议,我可以尝试另一种方法来测试速度? 在这种情况下,是否还有其他因素可能会减慢您的最终页面速度? javascript或图片可能吗? 我查看了 chrome 开发工具网络选项卡,它只有一个加载缓慢的 .php 文件,其余的几乎是即时的。 也许您可以尝试将 2 个函数(showNotifications 和 sortNotif)合二为一?我认为通过 while 循环迭代的多个 switch 语句可能会减慢整个代码的速度。 【参考方案1】:

计划改变,因为我误解了设置。

您将希望一次性从每个“类型”表中提取所有数据,而不是基于每个通知。这意味着您需要循环两次通知,一次获取所有 id 和适当的类型,然后第二次输出结果。

function showNotifications($userid)
    $STH = $this->database->prepare('SELECT * FROM notifications WHERE user_id = :userid ORDER BY timestamp DESC');
    $STH->execute(array(':userid' => $userid));

    // Centralized Book keeping for the types.
    // When you add a new type to the entire system, add it here as well.
    $types = array();

    // Add the first notification type
    $types["1"] = array();
    // "query" is pulling all the data you need concerning a notification
    $types["1"]["query"] = "SELECT m.id, u.username, u.firstname FROM messages m, users u WHERE m.sender = u.id AND m.id IN ";
    // "ids" will hold the relevant ids that you need to look up.
    $types["1"]["ids"] = array();

    // A second type, just for show.
    // $types["2"] = array();
    // $types["2"]["query"] = "SELECT a.id, u.username, u.firstname FROM articles a, users u WHERE a.sender = u.id AND a.id IN ";
    // $types["2"]["ids"] = array();

    // Use fetchAll to gather all of the notifications into an array
    $notifications = $STH->fetchAll();

    // Walk through the notifications array, placing the notification id into the corret
    // "ids" array in the $types array.
    for($i=0; $i< count($notifications); $i++)
        $types[$notifications[$i]['type']]["ids"][] = $notifications[$i]['feature_id'];
    

    // Walk through the types array, hit the database once for each type of notification that has ids.
    foreach($types as $type_id => $type)
        if(count($type["ids"]) >  0)
            $STH = $this->database->prepare($type["query"] . "( " . implode(",", $type["ids"]) . " )");
            $STH->execute();
            // Creates a hash table with the primary key as the array key
            $types[$type_id]['details'] = $STH->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC); 
            $types[$type_id]['details'] = array_map('reset', $types[$type_id]['details']);
            // run array_map to make it easier to work with, otherwise it looks like this:
            // $results = array(
            //     1234 => array(0 => array('username' => 'abc', 'firstname' => '[...]')),
            //     1235 => array(0 => array('username' => 'def', 'firstname' => '[...]')),
            // );
        
    

    // Now walk through notifications again and write out based on notification type,
    // referencing $types[<notification type>]["details"][<message id>] for the details
    for($i=0; $i< count($notifications); $i++)

        // check to see if details for the specific notification exist.
        if(isset($types[$notifications[$i]['type']]["details"][$notifications[$i]['feature_id']]))
            $notification_details = $types[$notifications[$i]['type']]["details"][$notifications[$i]['feature_id']];

            switch ($notifications[$i]['type']) 

                case "1":
                    echo '<li><i>'.timeAgo($notifications[$i]['timestamp']).'</i><a href="user.php?username=' . $notification_details['username'] . '">' . $notification_details['first_name'].'</a> sent you a <a href="inbox.php?message='.$notifications[$i]['feature_id'].'">message</a></li>';
                break;

            
        
    

更新:添加了逻辑以在未提取任何详细信息时跳过通知(例如,消息或用户已被删除)

我认为您想运行一个查询,通过连接或更复杂的 where 语句收集所有信息。

选项 1:这可能需要调整,以便不是表格的笛卡尔积

SELECT n.id, n.type, m.id, m.body, u.username, u.first_name 
FROM notifications n, messages m, users u
WHERE n.user_id = :userid AND m.id = n.feature_id AND u.id = m.sender

选项 2:如果表别名不起作用,那么您需要用完整的表名替换它们

SELECT SELECT n.id, n.type, m.id, m.body, u.username, u.first_name 
FROM notifications n
    JOIN messages m
        ON n.feature_id = m.id
    JOIN users u
        ON m.sender = u.id
WHERE n.user_id = :userid

【讨论】:

但是我以后会有很多不同类型的通知。用于消息、文章、用户等。我肯定不能一次获取所有信息吗? 发布一个必须“调整”的答案有什么意义? @Harry 你的所有types 会在messages 表中,发送者、接收者、正文的基本格式相同吗?如果是这样,那么应该没有问题。 @miah 不,我提到在“问题”标题下有多种类型的通知。例如,文章通知可能需要从不同的表中获取信息。这就是为什么我想使用 switch 功能,因为它很容易扩展为新类型的通知。 @Harry 我改变了我的答案,以提供一种可能的实现,即所有通知都命中数据库一次,然后为每种通知类型命中一次。

以上是关于优化包含 PDO 查询的 while 循环的主要内容,如果未能解决你的问题,请参考以下文章

PDO 在 while 循环内运行查询,仅显示 1 个结果

使用 PDO 驱动程序 PHP 在 sql-server 查询中循环

我可以应用哪些优化来加快此查询的速度?

PDO 和嵌套提取

优化具有 While 循环和交叉应用的 T-SQL 查询

将 PDO 与 MS SQL 结合使用“活动结果不包含任何字段”