Laravel 數量統計優化
有很多如下類似的需求,在一個部落格系統,部落格有分類,分類有個部落格數量的欄位 post_count
,通常會在部落格被髮布後統計並存入這個欄位。
我不清楚大佬們怎麼做的,反正我一般是在分類模型中定義一個這樣的方法:
public function freshPostCount()
{
$this->post_count = $this->posts()->count();
return $this->save();
}
非常清晰,非常簡單!
但是有個問題,如果有併發存在,可能會導致資料不一致,或者說不準確。作為一個有追求的渣渣,我想做點什麼來儘量降低不一致性的發生。
首先想到的是,用原生 SQL
:
public function freshPostCount()
{
\DB::update('UPDATE `pre_cates` cate SET `post_count` = (
SELECT count( * ) FROM `pre_posts` post WHERE `post`.`cate_id` = `cate`.`id` AND `post`.`is_published` = 1 )
WHERE `id` = '. $this->getKey());
}
看上去能解決點問題,但看著有點噁心,格格不入,毫無優雅可言。而且所有東西都寫死了,非常沒有彈性,那就讓他彈起來。
從上面的解決方案來看,統計和賦值只要執行一次就可以,那麼如此:
public function freshPostCount()
{
$this->update([
'post_count' => 'SELECT count( * ) FROM `pre_posts` WHERE `cate_id` = '. $this->getKey() .' `is_published` = 1'
]);
}
但是執行不起作用,發現框架把 SQL
語句繫結成引數,作為字串賦值了。於此同時我發現 Illuminate\Database\Query\Expression
類的存在,稍作修改:
public function freshPostCount()
{
$this->update([
'post_count' => new Expression('(SELECT count( * ) FROM `pre_posts` WHERE `cate_id` = '. $this->getKey() .' `is_published` = 1)'),
]);
}
可以執行,並且還把 updated_at
自動帶上了,但依然留有遺憾,能否利用 posts()
這個關聯關係,答案是可以的,不過比較麻煩:
public function freshPostCount()
{
$count_sql = $this->posts()->published()->selectRaw('count(*)')->toRawSql();
return $this->update([
'post_count' => new Expression("($count_sql)"),
]);
}
toRawSql()
是一個自定義的方法,獲取完整的執行 SQL:
\Illuminate\Database\Query\Builder::macro('toRawSql', function(){
return array_reduce($this->getBindings(), function($sql, $binding){
return preg_replace('/\?/', is_numeric($binding) ? $binding : "'{$binding}'", $sql, 1);
}, $this->toSql());
});
\Illuminate\Database\Eloquent\Builder::macro('toRawSql', function(){
return $this->getQuery()->toRawSql();
});
不知道各位大佬都是怎麼操作的,一起交流下啊