統計腳本梳理

  交代一下所遍歷的表的結構。表存在一個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斷開聯繫,不會因爲當前會話的關閉而停止執行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章