1.背景:
每天0點定時任務統計數據,實現目標是統計時如果沒有今天的統計數據,那就執行insert操作 如果存在那就執行update操作;
代碼邏輯
1 if(報表存在){ 2 update(); 3 }else{ 4 insert(); 5 }
上線跑了多天后 發現有時候會出現同一天的統計數據有2條
經過分析后
發現當0點定時任務跑的時候,存在用戶登錄(登錄需要統計,需要更新報表),因為0點的時候,數據庫都沒記錄,所以兩者都判斷了insert操作,造成數據庫有2條相同數據(只有登錄數量的字段不一樣,其他字段都一樣)
2.解決方案:
鎖
說實話,第一反應是給數據表加鎖。laravel提供了2個鎖:sharedLock和lockForUpdate.經過仔細研究分析后發現,這2個鎖並不能解決我目前的需求,原因如下:
- sharedLock 對應的是 LOCK IN SHARE MODE
- lockForUpdate 對應的是 FOR UPDATE
這2個鎖都是避免同一行數據被其他transaction進行update()的。
- sharedLock 不會阻止其他 transaction 讀取同一行
- lockForUpdate 會阻止其他 transaction 讀取同一行 (需要特別注意的是,普通的非鎖定讀取讀取依然可以讀取到該行,只有 sharedLock 和 lockForUpdate 的讀取會被阻止。)
但是我現在的實際情況都沒有讀到數據,所以都不能滿足。
后來實在沒辦法想到了表鎖,對,就是把全表都鎖定了。但是這個效率性能實在太差了,而且在LOCK TABLE 和UNLOCK TABLE之間有異常,會導致鎖無法釋放。潛在問題很嚴重,所以還是不打算用這個方法。
INSERT IGNORE
經查閱大量資料(瘋狂百度)后發現了insert ignore。insert ignore 如果存在數據,那么則忽略新數據
REPLACE INTO
replace into 表示插入替換數據,表中如果有PrimaryKey或者unique索引的話,數據庫如果已存在數據,則用新數據替換,如果沒有數據則和insert into一樣。
上面2個方法固然好,但是存在數據丟失的問題,還是放棄了。
INSERT INTO
最終的解決辦法居然回到了最原始的sql語句。insert into表示插入數據,數據庫會檢查主鍵(PrimaryKey),如果出現重復會報錯;
對就是利用這個主鍵報錯的機制,解決了的需求。就是在每次插入數據的時候把下一個自增id也插入進去。因為我們平時插入數據都是不用管自增id的,所以laravel都沒有獲取下一個自增id的封裝方法。沒辦法,自己動手豐衣足食。
//獲取下一個自增id,方法還是很簡單的
1 public static function getIncrementId() 2 { 3 $id = self::query() 4 ->orderByDesc('id') 5 ->value('id'); 6 return ++$id; 7 }
1 try { 2 $report = DailyReport::getReport($customer->id, 1); 3 if (!$report) { 4 $id = DailyReport::getIncrementId(); 5 DailyReport::create([ 6 'customer_id' => $customer->id, 7 'report_date' => Carbon::today(), 8 'id' => $id,//把id也插入 9 ... 10 ]); 11 } 12 } catch (\Exception $exception) { 13 if (23000 == $exception->getCode()){//這里捕獲主鍵重復的錯誤,然后做相關邏輯的操作 23000后來發現好像也別的sql錯誤也會報,記得優化 14 //do something 15 }else{ 16 LogManagers::error($exception); 17 } 18 }
上面就是解決的代碼。最終的解決辦法還是利用了主鍵重復的錯誤來解決問題。
本文屬於個人原創,歡迎轉載,轉載請附鏈接:https://www.cnblogs.com/x-x-j/p/13491564.html