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