统计脚本梳理

31 篇文章 0 订阅
4 篇文章 0 订阅
  交代一下所遍历的表的结构。表存在一个id的自增主键索引,然后要查找出激活种类为1以及激活时间小于2016的数据。
  第一次使用的是代码中经常使用的方式,也就是要保证代码的整体性,一致性,毕竟一个整体风格一致的代码要比牺牲性能更好一些吧。

  所以,下面交代这个符合整体风格的代码.这种查询在where语句中并没有用到任何索引,相比较便利整个表(去掉where条件),这中方式更糟糕。因为如果是遍历整个表的话,可能只是可能MySQL内部还会用到索引,但是这种方式肯定用不到了。性能当然会更糟糕。

function getDeviceId($begin, $limit, $mysqlConfig)
{
    $queryInstance = QFrameDB::getInstance($mysqlConfig);
    $deviceSql = "SELECT id, title, FROM t_state WHERE active_state = ? 
                  AND active_time < '2016-01-01 00:00:00' LIMIT {$begin}, {$limit}";
    return $queryInstance->getAll($deviceSql, 1);
}

  有必要介绍一下limit的用法。limit 1020执行这条语句,MySQL默认会查询30条语句,刨去前面的10个记录,返回后面的20条记录。我并不赞成上面的limit的做法,即使是遍历整个数据表,但是我还是被保持整体风格说服了。

  这里可能是我思维上的一个误区。将全表扫描和按条件扫描的limit混淆了。不过,我也确实不知道他们之间在性能上的差别到底有多大。
  
  下面是我使用的方式。这种方式使用了主键id作为判断条件,每次取出大于id的pagesize条记录,然后循环改变id的值(就是保证将最后记录的id值赋值给id)。这里使用了id作为索引,当然这也是自己explain之后的结果,确实是使用到了索引。

  还要解释一点,使用id的这种limit方式肯定在性能上要优于 limit 10, 20这种方式。

function getDeviceId($id, $pageSize, $mysqlConfig)
{
    $queryInstance = QFrameDB::getInstance($mysqlConfig);
    $deviceSql = "SELECT id, title FROM t_state WHERE id > ? AND active_state = 1
                  AND active_time < '2016-01-01 00:00:00' ORDER BY id ASC LIMIT {$pageSize}";
    return $queryInstance->getAll($deviceSql, array($id, 4));
}

$id = 0;
$pageSize = 3000;
$maxId = 1000000000; //从数据库中统计的结果

$startTime = time();
$logger = Logger::getRootLogger();
$logger->info("elder start:$startTime");
$insertSql = "INSERT INTO t_year ( local ) VALUES ( ? )";
do {
    $devices = getDeviceId($id, $pageSize, $mysqlConfig);
    if (!empty($devices)) {
        foreach ($devices as $key => $device) {
            try {
                //获取当前遍历到的id
                $id = $device['id'];
                $logger->info("id : " . $device['id']);
                //....
            } catch (Exception $e) {
                $logger->info("error occur id : " . $device['id']);
            }
        }
    } else {
        $logger->info("elder job end time:" . time());
        break;
    }
    $logger->info("one cost time :" . (time() - $startTime));
} while ($id <= $maxId);

  上面的代码是最终的合理结果。但是过程中确实出现了一些细节问题。交代一下上面的参数maxId是我在数据库里select max(id) from t_device_state 获取的结果。这确实是一个常量,我觉得这样做当然也是可以的。但是问题是我之前的代码是这样写的:

do {
    $devices = getDeviceId($id, $pageSize, $mysqlConfig);
    if (!empty($devices)) {
        foreach ($devices as $key => $device) {
            try {
                //获取当前遍历到的id
                $id = $device['id'];
                $logger->info("id : " . $device['id']);
                //...
            } catch (Exception $e) {
                $logger->info("error occur id : " . $device['id']);
            }
        }
    }
    $logger->info("one cost time :" . (time() - $startTime));
} while ($id <= $maxId);
$logger->info("elder job end time:" . time());

  好好看看,会发现他是一个死循环。当然唯一的好处是,虽然确实是一个死循环,但是他统计的结果是正确的。死循环只会在统计到最后一个id之后,一只是用该id进行循环,但是一直没有结果,却还是一直循环。为什么会这样了,首先是我统计的最大id统计的是整个数据库的,而不是符合条件的记录最大id。然后我修改成了当数据集结果为 empty 的时候barak的方式。

  中间还有一个需要注意的就是:在每一个foreach后面会包裹一个try-catch语句。这样保证如果有一条语句挂了,后面的语句还是可以继续执行的。到执行最后,看看这些异常的语句是不是需要修复。

  总结这次的job:
  1. 分清楚这两种统计的不同方式。全表扫描和使用id按条件扫描。因为他们对于退出循环的条件是完全不同的,要保持清醒,不要混淆。
  2. 认真分析循环结束的条件,是否会按照想象的方式结束循环。这当然是相当重要的了。好好想象是否真的能够达到退出循环的条件。
  3. 确保每次的循环记录有try-catch包裹,让job不会因为异常而退出。
  4. 要统计执行一次循环所花费的时间。比如这个例子,统计3000条记录会花费的时间,当然他的统计是有瑕疵的,因为是从job开始到每次循环结束的时间。因为这样可以估计job到底要花费多少时间来执行。
  5. 确保job能够随时停止和随时开始。这个例子中,每次停止之后修改id为上一次停止时的id就可以做到随时停止随时开始。
  6. 当然是linux层面的,确保脚本跟当前session断开联系,不会因为当前会话的关闭而停止执行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值