【问题标题】:MySQL PHP Query Optimization - FeedbacksMySQL PHP 查询优化 - 反馈
【发布时间】:2023-03-19 08:01:02
【问题描述】:

我需要每天运行这个脚本(cron)来更新 1 个月的反馈总量(至少这是我现在设计的)。这是我的代码。有人对我应该如何解决这个问题有更好的了解吗?也许改变我的处理方式或优化我的 updateMonthlyFeedback.php 脚本?

updateMonthlyFeedback.php

session_start();
include("db.php");

$sql="SELECT MAX(uid) as maxUID FROM users";
$result = mysql_query($sql) or die(mysql_error());
$row = mysql_fetch_array($result);
$maxUID = $row['maxUID'];

for($i=0;$i<$maxUID;$i++){
    $sql="SELECT COUNT(*) as negativeCount FROM feedbacks WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = -1 AND uid = '$i'";
    $result = mysql_query($sql) or die(mysql_error());
    $row = mysql_fetch_array($result);
    $negativeCount = $row['negativeCount'];
    $sql="SELECT COUNT(*) as neutralCount FROM feedbacks WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = 0 AND uid = '$i'";
    $result = mysql_query($sql) or die(mysql_error());
    $row = mysql_fetch_array($result);
    $neutralCount = $row['neutralCount'];
    $sql="SELECT COUNT(*) as positiveCount FROM feedbacks WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = 1 AND uid = '$i'";
    $result = mysql_query($sql) or die(mysql_error());
    $row = mysql_fetch_array($result);
    $positiveCount = $row['positiveCount'];
    $sql = "UPDATE feedback_totals SET negativeCount = '$negativeCount', neutralCount = '$neutralCount', positiveCount = '$positiveCount' WHERE uid = '$i'";
    $result=mysql_query($sql) or die(mysql_error());
}

MySQL 表

CREATE TABLE feedback_totals (
    uid                     VARCHAR(40),
    negativeCount           int,
    neutralCount            int,                
    positiveCount           int,
    halfStarCount           int,
    oneStarCount            int,
    oneHalfStarCount        int,
    twoStarCount            int,
    twoHalfStarCount        int,
    threeStarCount          int,
    threeHalfStarCount      int,
    fourStarCount           int,
    fourHalfStarCount       int,
    fiveStarCount           int,
    PRIMARY KEY             (uid)
    #FOREIGN KEY            (uid) REFERENCES users(uid) ON DELETE CASCADE
);

CREATE TABLE feedback_last_month (
    uid                     VARCHAR(40),
    negativeCount           int,
    neutralCount            int,                
    positiveCount           int,
    halfStarCount           int,
    oneStarCount            int,
    oneHalfStarCount        int,
    twoStarCount            int,
    twoHalfStarCount        int,
    threeStarCount          int,
    threeHalfStarCount      int,
    fourStarCount           int,
    fourHalfStarCount       int,
    fiveStarCount           int,
    PRIMARY KEY             (uid)
    #FOREIGN KEY            (uid) REFERENCES users(uid) ON DELETE CASCADE
);

CREATE TABLE feedback (
    feedback_id             INT NOT NULL AUTO_INCREMENT,
    uid                     VARCHAR(40),INDEX (uid),
    sender_id               VARCHAR(40),
    type                    int,                #-1 = neg, 0 = neutral, 1 = positive
    starCount               VARCHAR(40),
    description             VARCHAR(80),
    date_created            timestamp DEFAULT CURRENT_TIMESTAMP, 
    fromType                VARCHAR(40), # buyer or seller
    fromUsername            VARCHAR(40),
    PRIMARY KEY             (feedback_id)
    #FOREIGN KEY            (uid) REFERENCES users(uid) ON DELETE CASCADE
);

【问题讨论】:

  • 那么显而易见的第一个答案是删除 for 循环,而不是通过 uid 限制查询,group on uid。
  • 您确定要从 users 中选择 COUNT() 而不是计算 feedback 行的数量吗?
  • 是的,我更新了这个。刚刚写了一个快速原型,还没有测试。只是想有一些东西来解释我的问题。

标签: php mysql performance apache optimization


【解决方案1】:

好的,正如其他人所说,使用 PDO/MYSQLI。但是,使用您已经拥有的代码,这里有两种方法可能会更好地工作和执行。

首先是使用相关子查询来获取负/正/中性值。这很好,因为它很短,但它绝不是理想的。您仍在对数据库执行大量查询(每个 uid + 初始更新 3 个)。但是,您只是从 php 向服务器发送一个查询,并让数据库完成所有其余工作。这可能对少数用户来说效果很好,但过一段时间就会开始出现性能问题。这一查询将更新 feedback_totals 中的所有行。但是,如果 feedback_totals 中没有一行,则不会为任何新的 uid 插入新行。

//one query, this is it. updates it all.
$sql = "UPDATE `feedback_totals`
        SET
            `negativeCount`=(SELECT COUNT(*) FROM `users` WHERE `uid`=`feedback_totals`.`uid` AND `date_created` >= (CURDATE() - INTERVAL 30 DAY) AND `type`=-1),
            `positiveCount`=(SELECT COUNT(*) FROM `users` WHERE `uid`=`feedback_totals`.`uid` AND `date_created` >= (CURDATE() - INTERVAL 30 DAY) AND `type`=1),
            `neutralCount`=(SELECT COUNT(*) FROM `users` WHERE `uid`=`feedback_totals`.`uid` AND `date_created` >= (CURDATE() - INTERVAL 30 DAY) AND `type`=0)";
$result=mysql_query($sql) or die(mysql_error());

从长远来看,第二个可能更好。在一次查询中查询出所有需要的数据。循环遍历该结果,将其格式化为 php,遍历该结果并进行更新。这个可能会执行得更好,因为您运行的查询数量要少得多(1 用于获取数据 + 1 用于每个 uid)。

//query for all the data
$sql="SELECT
            `uid`,
            `type`,
            COUNT(*) AS cnt
        FROM `users`
        WHERE `date_created` >= (CURDATE() - INTERVAL 30 DAY)
        GROUP BY `uid`,`type`";
$result=mysql_query($sql) or die(mysql_error());

$data = array();
//loop through the result
while($row=mysql_fetch_assoc($result)){
    //if the uid is not in $data
    if(!isset($data[$row['uid']])){
        //add it with a blank array
        $data[$row['uid']] = array('negativeCount'=>0,'neutralCount'=>0,'positiveCount'=>0);
    }
    //add to the data for this uid depending on type
    if($row['type']==-1){
        $data[$row['uid']]['negativeCount']=$row['cnt'];
    } elseif($row['type']==1){
        $data[$row['uid']]['positiveCount']=$row['cnt'];
    } else {
        $data[$row['uid']]['neutralCount']=$row['cnt'];
    }
}

//now loop through the data and update the table
foreach($data as $uid=>$cnt){
    $sql = "UPDATE `feedback_totals`
            SET
                `negativeCount`={$cnt['negativeCount']},
                `positiveCount`={$cnt['positiveCount']},
                `neutralCount`={$cnt['neutralCount']}
            WHERE `uid`=$uid";
    $result=mysql_query($sql) or die(mysql_error());
}

【讨论】:

  • 好的,我明白了。但是从我已经拥有的代码之外。会有更好的方法来解决这个问题吗?仅供参考,反馈总数在创建用户时初始化。
  • 我提供的代码只是对您的代码的重新配置。它应该可以直接替代。两者都应该工作(我没有你的数据库可以测试)并且仍然比你拥有的代码更好。
  • 我应该每天更新这些总数还是有更好的方法?
  • 一般来说,如果您可以根据数据库中的数据计算总计,则不需要将总计存储在其他地方。那么这只是重复。对于任意数量的用户而言,查找过去 30 天的 count(*) 确实不会让数据库在需要时即时查询。
  • 我只是认为预处理这 1 个月的总数比不断地对数据库进行计数要好。我可能只是在实时而不是白天使用它。
【解决方案2】:

这里显然有很多重复。其中大部分可以通过重构代码来删除,但作为一个起点,即使是当前流程,您也可以通过使用更好的数据库 API 使其性能更好。

所以我建议的第一件事是停止使用mysql_xxx() 函数,而改用PDO 库。旧的mysql 函数无论如何都已被弃用,因此尽可能不建议使用它们,但在这种情况下,使用PDO 是有特定原因的,因为它比旧函数具有显着的性能优势。

PDO 允许您使用名为 Prepared Queries 的功能,如果您重复调用类似的查询,则可以让数据库更有效地缓存查询。

其次,查询本身。是的,这些绝对可以简化。循环中的三个查询可以使用GROUP BY 组合成一个查询。查询看起来像这样:

SELECT COUNT(*) FROM users
WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY )
AND uid = :uid
AND type = -1 OR type = 1 OR type = 1
GROUP BY type

您应该从此查询中获得与要获取的三个记录相同的三个值。

您可以做的还有很多,但这是一个好的开始。我相信您会得到其他答案来进一步帮助您。

希望对您有所帮助。

【讨论】:

    【解决方案3】:

    我会运行三个查询来获取您需要的信息:

    SELECT uid, COUNT(*) as negativeCount FROM users
    WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = -1 
    GROUP BY uid ORDER BY uid ASC";
    
    SELECT uid, COUNT(*) as neutralCount FROM users
    WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = 0
    GROUP BY uid ORDER BY uid ASC";
    
    SELECT uid, COUNT(*) as positiveCount FROM users
    WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = 1 
    GROUP BY uid ORDER BY uid ASC";
    

    然后我会遍历结果,当它们与当前 uid 对齐时递增行。返回结果的一个棘手之处是缺少 uid 表示计数为 0。但它们是有序的,因此您会知道增加返回的结果(通过您正在使用的 fetch_row)或每个索引的单独索引结果。

    看起来像这样:

    // Have to load up the first result
    $p_row = mysql_fetch_array($positive_result);
    $neu_row = mysql_fetch_array($neutral_result);
    $neg_row = mysql_fetch_array($negative_result);
    
    for($i = 0; $i < $maxUID; $i++){
      $positive = $neutral = $negative = 0;
      if($p_row[0] == $i){
        $positive = $p_row[1];
        $p_row = mysql_fetch_array($positive_result);
      }
      if($neu_row[0] == $i){
        $neutral = $neu_row[1];
        $neu_row = mysql_fetch_array($neutral_result);
      }
      if($neg_row[0] == $i){
        $negative = $neg_row[1];
        $neg_row = mysql_fetch_array($negative_result);
      }
      $sql = "UPDATE feedback_totals SET negativeCount = '$negative', neutralCount = '$neutral', positiveCount = '$positive' WHERE uid = '$i'";
      mysql_query($sql) or die(mysql_error());
    }
    

    【讨论】:

      【解决方案4】:

      所有这些都可以通过一个 SQL 查询来完成:

      INSERT INTO `feedback_totals` (uid,negativeCount,positiveCount,neutralcount)
      SELECT users.uid, 
        COUNT(neg.feedback_id) as negativeCount, 
        COUNT(pos.feedback_id) as positiveCount, 
        COUNT(neut.feedback_id) AS neutralCount
      FROM users
      LEFT JOIN feedback neg  ON neg.uid =users.uid AND neg.type=-1 AND neg.date_created >=(CURDATE() - INTERVAL 30 DAY)
      LEFT JOIN feedback pos  ON pos.uid =users.uid AND pos.type=1  AND pos.date_created >=(CURDATE() - INTERVAL 30 DAY)
      LEFT JOIN feedback neut ON neut.uid=users.uid AND neut.type=0 AND neut.date_created>=(CURDATE() - INTERVAL 30 DAY)
      GROUP BY uid
      ON DUPLICATE KEY UPDATE 
        negativeCount=VALUES(negativeCount), 
        positiveCount=VALUES(positiveCount), 
        neutralCount=VALUES(neutralCount);
      

      ON DUPLICATE KEY UPDATE 将允许查询添加新行以及更新现有行。你可以在SQL Fiddle上玩这个

      【讨论】:

        【解决方案5】:

        首先尝试将您的 Mysql 更改为 mysqli

        只需将您的 db.php 更改为:

        $mysqli = new mysqli("localhost", "XXX", "XXX", "XXX");
        
        if ($mysqli->connect_errno) {
            printf("Connect failed: %s\n", $mysqli->connect_error);
            exit();
        }
        

        并更改您的代码:

        session_start();
        include("db.php");
        $sql="SELECT MAX(uid) as maxUID FROM users; ";
        
        for($i=0;$i<$maxUID;$i++){
            $sql.="SELECT COUNT(*) as negativeCount FROM users WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = -1 AND uid = '$i'; ";
            $sql.="SELECT COUNT(*) as neutralCount FROM users WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = 0 AND uid = '$i'; ";
            $sql.="SELECT COUNT(*) as positiveCount FROM users WHERE date_created >= ( CURDATE() - INTERVAL 30 DAY ) AND type = 1 AND uid = '$i'; ";
            $sql.="UPDATE feedback_totals SET negativeCount = '$negativeCount', neutralCount = '$neutralCount', positiveCount = '$positiveCount' WHERE uid = '$i'; ";
        }   
        
        if ($mysqli->multi_query($sql)) {
            do {
                $rows=array();
                if ($result = $mysqli->store_result()) {
                    while($rows[] = mysqli_fetch_assoc($result));
                    array_pop($rows); 
                    $result->free();
                }
                $data[]=$rows;
            } while ($mysqli->next_result());
            print_r($data);
        } else
            echo "Error with SQL";
        

        这只会与数据库建立一个批量连接,并将打印数组中的所有数据。

        【讨论】:

        • 如果你还是要改变 DB API,你不妨选择 PDO;比mysqli好。
        猜你喜欢
        • 2011-10-27
        • 1970-01-01
        • 2011-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-22
        • 2011-07-07
        • 2018-12-21
        相关资源
        最近更新 更多