記錄不存在則插入,存在則更新 → MySQL 的實現方式有哪些?

開心一刻

  今天我爸、我、我女兒一起喫飯,我們每人一個雞腿

  女兒問道:爸爸,你喫雞腿嗎

  我以爲她要把她的雞腿給我喫,倍感欣慰地說道:我不喫,寶貝

  女兒一把抓起我的雞腿放進了她爺爺的碗裏,說道:不喫給爺爺喫

  我沒想到她會來這一出,我從我爸碗裏夾回我的雞腿,對女兒說道:不是,你這樣問問你爺爺

  女兒向她爺爺問道:爺爺,你喫雞腿嗎

  我爸一臉溺愛的說道:喫

  女兒又一把抓起我的雞腿放進了她爺爺的碗裏,說道:爺爺喫,給爺爺

  我一臉不可思議的看着我女兒,竟然套路我,那我就陪你玩到底,我又從我爸碗裏夾回雞腿,對女兒說道:不對不對,讓你問懵了,你再問我一次

  女兒問道:爸爸,你喫公雞腿嗎

  我信誓旦旦地說道:我喫

  女兒又一把抓起我的雞腿放進了她爺爺的碗裏,說道:這是母雞腿,你去找公雞腿喫

  我徹底懵了

需求背景

  環境

  MySQL 版本: 5.7.20-log 

  開發規範

  公司後端開發規範有這麼一點:

    更新數據庫表中數據的時候,不允許先刪,然後批量插入

    需要將入參與表中數據比判斷,找出哪些是新插入,哪些需要更新,哪些是刪除的,然後再做對應的數據操作

  需求

  我們有表如下:

  當商品配送完後之後,需要記錄它的最新配送價,若商品最新配送價已經存在則進行更新,不存在則執行插入

  針對這個需求,我們有哪些實現方式?

代碼處理

  按開發規範中說的處理

  通過代碼在內存中進行數據處理,找出插入列表與更新列表,然後執行數據庫操作

  因爲是很常規的插入與更新操作,所以這種處理方式適用於所有的關係型數據庫

REPLACE INTO

  當數據庫是 MySQL ,碰到 不存在則插入,存在則更新 的需求時,第一時間往往想到的是 REPLACE INTO 

  工作原理

   replace into 跟 insert 功能類似

  不同點在於: replace into 首先嚐試插入數據到表中,如果發現表中已經有此行數據(根據主鍵或者唯一索引判斷)則先刪除此行數據,然後插入新的數據,否則直接插入新數據

   replace 語句會返回一個數,表示受影響的行的數目,該數是被刪除和被插入的行數的和

  我們來看個示例

  對於示例結果,相信大家都能理解

  需要強調的是:根據唯一索引 uk_comany_ware 判定 (1001,10001,19.8,1,1) 已經存在,那麼先刪除此記錄,然後插入 (1001,10001,20.5,1,1) 

  而 (1001,10002,5.45,1,1) 判定爲不存在,那麼直接插入

  這就導致我們看到的輸出結果是: 受影響的行:3 ,同時自增主鍵由 1 變成了 2 3 ,而不是 1 2 

  有坑

  正是因爲 replace into 的工作原理,不可避免就產生了一些需要注意的地方

  1、破壞外鍵約束

    如果主鍵被指定成了其他表的外鍵,那麼 replace into 更新(非插入)時影響到了其他表的外鍵約束,那麼會執行失敗,提示類似信息:

    可能很多小夥伴會說:我們開發過程中,會遵循阿里開發手冊中的規約,其中有一條規約如下:

    我們不用外鍵了,也就不會出現前面的 [Err] 1451 錯誤了

    其實阿里開發手冊中的這條規約,不是說不讓我們用外鍵,而是說不用數據庫層面的外鍵約束,在應用代碼層面解決外鍵邏輯

    用數據庫層面的外鍵,問題提示的很明顯,也不會產生髒數據

    而應用層解決外鍵,反而使外鍵約束的數據一致性問題更隱晦,產生髒數據,如下

    從此我們踏上了修數據的不歸路

  2、主鍵加速自增

    很多情況下,我們的主鍵是 int 或者 bigint 類型,並且設置成了自增

    不管是 int 還是 bigint ,都有一個最大值,如果一直自增下去,總有一天會達到最大值(可能到地老天荒也達不到這個值) 

    replace into 的更新是先刪除再插入,會導致主鍵自增 1(照理來說,更新是不應該導致主鍵自增 1)

    如果更新頻率遠遠大於插入頻率,本不用考慮的自增主鍵用完的問題,可能就需要考慮了

    另外也會導致主鍵不連續,主鍵值跳躍式的出現在表中

  3、主從切換問題

    master:master-local ,slave:slave-192.168.0.112 ,同步庫:my_project 

    從上圖可以看出,主從複製是正常的

    接下來我們看看 replace into 對主從複製有什麼影響

    此時 master 與 slave 上的 t_ware_last_delivery_price 的下一個非手工指定的主鍵都是 11( AUTO_INCREMENT=11 ),兩者是一致的

    我們在 master 上使用 replace into 更新一條記錄

     master 與 slave 的數據是一致的,但是 master 上的下一個自增主鍵是 AUTO_INCREMENT=12 ,而 slave 上卻是 AUTO_INCREMENT=11 

    可能會有人覺得:數據一致就行,下一個自增主鍵不一致有什麼關係?

    我們來想一下這個問題:如果 master 庫崩了,我們會怎麼做?會將 slave 提升爲 master 

    此時問題就來了: slave 提升成 master 之前,實際數據的 id 已經到了 11 ,但其 AUTO_INCREMENT=11 ,也就說下一個自增主鍵是 11 

      那麼下一條不指定 id 值的新紀錄是插入時就會發生 duplicate key error ,每次衝突之後 AUTO_INCREMENT += 1,直到增長爲 max(id) + 1 之後才能恢復正常

INSERT UPDATE

  針對 不存在則插入,存在則更新 , MySQL 還提供了另外一種方言實現: INSERT ... ON DUPLICATE KEY UPDATE Statement 

  工作原理

  如果指定 ON DUPLICATE KEY UPDATE 子句,並且要插入的行將導致唯一索引或主鍵中出現重複值,則會更新舊行,否則則是插入

  例如,如果 列 a 被聲明爲唯一且包含值 1,則以下兩條語句具有類似的效果

  但是這兩條 SQL 的效果並不完全相同,我們以 t_ware_last_delivery_price 爲例,來看看它們的區別

  我們先來看看 UPDATE 

  只是對 id = 11 的 last_delivery_price 就行了修改,受影響的行只有 1,不會影響 AUTO_INCREMENT 的值

  我們再來看看 INSERT INTO ... ON DUPLICATE KEY UPDATE 

  對 id = 11 的 last_delivery_price 進行了修改,受影響的行是 2,並且 AUTO_INCREMENT=13 

  此刻,我相信我們有共同的兩個疑問

    1、爲什麼受影響的行數是 2,而不是 1

    2、自增主鍵爲什麼自增了 1( AUTO_INCREMENT 爲什麼等於 13,而不是原有的 12)

  爲什麼受影響的行數是 2,而不是 1,官方文檔有這麼一段說明

  意思就是:1 表示新插入一行,2 表示更新了一行,0 表示更新前後值未變

  我們換個角度來理解,假設讓我們來設計,一條 SQL 既能插入,也能更新,我們如何告知用戶到底是插入成功了,還是更新成功了?

  所以 1,2 僅僅只是用來區分插入和更新,2 並非真正受影響的行數

  主鍵明明沒有變化,爲什麼 AUTO_INCREMENT=13 自增了 1 ?

  這和 MySQL 的主鍵自增的參數有關 innodb_autoinc_lock_mode ,它有 3 個值 0,1,2 

   mysql5.1 之後其默認值是 1

  因爲 innodb_autoinc_lock_mode = 1 

  所以上述 SQL 被當作簡單插入處理,在真正修改數據之前就對 AUTO_INCREMENT 自增 1 處理了

  批量操作

  不僅支持單條操作,也支持批量操作

  和批量插入類似

  有坑

  因爲 innodb_autoinc_lock_mode = 1 是一個折中的選擇,一般不會去改它,所以有些需要注意的點

  1、主鍵加速自增

  與 replace into 類似,即使是更新,也會導致 AUTO_INCREMENT 自增,加速了主鍵的衰老

  同時也會導致主鍵的跳躍

  2、主從切換問題

  與 replace into 類似, master 上的更新導致 AUTO_INCREMENT 自增,而 AUTO_INCREMENT 又未同步到 slave 

  當 slave 升級成 master 後,可能會出現 duplicate key error 

  與 replace into 不同的是,上述兩個問題可以通過設置 innodb_autoinc_lock_mode = 0 來避免,因爲很多場景下對性能要求並不高

總結

  1、如何選擇哪種方式

    上述三種方式各有優略,代碼處理不依賴於具體的數據庫,可移植性高,也不會引入特定數據庫的在這方面的缺陷

     replace into 的方式不推薦(坑有點多),它完全可以由 INSERT UPDATE 替代

     INSERT UPDATE 可以減少我們的代碼,但它是 MySQL 的拓展實現,只有 MySQL 支持,可移植性差

  2、針對 INSERT UPDATE 的 “坑”,我們可以結合具體的業務來設置 innodb_autoinc_lock_mode ,適當的避免它的 “坑”

  3、道路千萬條,合適第一條

    針對某個需求,實現方式往往有很多,我們要做的就是從中找到最適合的那一條

參考

  REPLACE Statement

  INSERT ... ON DUPLICATE KEY UPDATE Statement

  mysql自增id超大問題查詢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章