laravel 解決mysql插入相同數據的問題


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM