Laravel 數量統計優化

chuoke發表於2019-06-16

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();
});

參考:https://gist.github.com/JesseObrien/741898...

不知道各位大佬都是怎麼操作的,一起交流下啊

初出茅廬,一知半解,望有識之士多多指教。抱拳...

相關文章