最近在優化代碼的時候,突然想起來TP5的數據庫操作中有個cache,之前也用過,印象里就是在緩存時間內,請求的速度會大大加快,但是修改數據會導致不能及時更新。當初還比較年輕,沒有深入去搞清楚,只是不再使用cache了而已,現在剛好有機會,就來稍微學一學吧。
很可惜,不論是官方文檔還是網上搜索出來的結果,基本上都只是告訴我們如何去使用它,完全沒有說到它的工作原理之類的,無奈,只能去慢慢讀源碼了。
首先讓我感到疑惑的是,這個cache和我們平常用的緩存Cache有什么區別?
如果單單從功能上看的話,好像兩者沒有任何區別,都能設置key值和有效期,以及打標簽也都支持。隨后我做了個實驗,手動設置key名,然后用cache()助手函數去讀取,結果如我所料,讀取出來的結果果然是select的結果。
$result = db('my_table')->where($where)->cache('key',10)->select();
var_dump(cache('key'));? //結果和$result一樣
不過實驗歸實驗,還是得去源碼里看看具體是如何實現的。
進入/thinkphp/library/think/db/Query.php中,找到cache方法,可以看到,這里只是設置了屬性,真正的使用還不在這里,還得去select、find、value、column里面看。
//源碼太長了,就不一一復制了public function cache($key = true, $expire = null, $tag = null){? ? // 增加快捷調用方式 cache(10) 等同于 cache(true, 10)? ? if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) {? ? ? ? $expire = $key;? ? ? ? $key? ? = true;? ? }? ? if (false !== $key) {$this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag];}? ? return $this;}public function select($data = null){? ? ......? ? if (empty($options['fetch_sql']) && !empty($options['cache'])) {? ? ? ? // 判斷查詢緩存? ? ? ? $cache = $options['cache'];? ? ? ? unset($options['cache']);$key? ? ? = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind));? ? ? ? $resultSet = Cache::get($key);}? ? ......? ? if (isset($cache) && false !== $resultSet) {// 緩存數據集
? ? ? ? $this->cacheData($key, $resultSet, $cache);}? ? ......}protected function cacheData($key, $data, $config = []){? ? if (isset($config['tag'])) {? ? ? ? Cache::tag($config['tag'])->set($key, $data, $config['expire']);? ? } else {Cache::set($key, $data, $config['expire']);}}
結論:可以很明顯地看出,不論是寫入緩存還是從緩存中讀取,都是與我們常用的Cache一樣的,唯一不同的是,如果你不指定key名的話,他會根據操作的數據庫名、表名以及主鍵ID等信息,幫你生成一個32位密文,你也不用擔心萬一key名重復導致緩存覆蓋了。
疑惑二:當數據更新時,緩存會怎么樣呢?
按照文檔上的說法,要么手動在update等更新操作中添加cache,來實現手動更新緩存;要么使用find方法并且使用主鍵查詢,就會自動清理緩存。手動指定緩存倒沒什么問題,除了不觸及緩存操作的新增之外,在數據更新后緩存都會被清除,然后在查詢時重新被寫入。PS:增刪改查中,新增操作是不觸及緩存的,這也是緩存要謹慎使用的原因,雖然能夠極大地增加效率,但是不能反映數據的及時更新。
然后就是什么情況下更新數據能夠自動清除緩存的問題了。比較麻煩的是,涉及到緩存操作的話,是否使用主鍵作為查詢條件還不一樣。也就是說,會有以下2*2+2*2共八種組合。
用主鍵做條件進行查詢+用主鍵做條件進行修改——清除
用主鍵做條件進行查詢+用主鍵做條件進行刪除——清除
用主鍵做條件進行查詢+不用主鍵做條件進行修改——不清除
用主鍵做條件進行查詢+不用主鍵做條件進行刪除——不清除
不用主鍵做條件進行查詢+用主鍵做條件進行修改——不清除
不用主鍵做條件進行查詢+用主鍵做條件進行刪除——不清除
不用主鍵做條件進行查詢+不用主鍵做條件進行修改——不清除
不用主鍵做條件進行查詢+不用主鍵做條件進行刪除——不清除
雖然還有很多情況沒有測試到,比如更新操作的數據是否為緩存的數據、查詢和更新操作的條件是不是一樣等等,即使測試的結果和文檔上描述的一樣,但是還是感覺說服力不夠強,還得再去源碼里找找原因。這里以update操作為例。
//如果有設置緩存名,則直接從cache中讀取
if (isset($options['cache']) && is_string($options['cache']['key'])) {
? ? $key = $options['cache']['key'];
}
......
//如果沒有手動設置緩存,則只能依靠主鍵ID以及操作的表來識別,否則沒有辦法識別出來
} elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) {
? ? $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind);
}
結論:只有當查詢和修改操作都使用主鍵ID作為條件時,才能實現自動清除緩存。
所以說,數據庫緩存并不是隨便用的,如果使用不當,很容易影響數據的時效性和用戶的體驗。如果真的有必要使用的話,最好還是不要偷懶用自動清除緩存,還是手動設置緩存名字,以及在更新操作時指定清除哪個緩存。
好了,不知不覺又花了半個晚上,今天就這樣吧,洗洗睡了。
另外,如果你有興趣,或者是有問題想要與我探討,歡迎來訪問我的博客:https:mu-mu.cn/blog