Mysql on duplicate key update用法及優缺點


  在實際應用中,經常碰到導入數據的功能,當導入的數據不存在時則進行添加,有修改時則進行更新,

  在剛碰到的時候,一般思路是將其實現分為兩塊,分別是判斷增加,判斷更新,后來發現在mysql中有ON DUPLICATE KEY UPDATE一步就可以完成(Mysql獨有的語法)。

ON DUPLICATE KEY UPDATE單個增加更新及批量增加更新的sql

在MySQL數據庫中,如果在insert語句后面帶上ON DUPLICATE KEY UPDATE 子句,而要插入的行與表中現有記錄的惟一索引或主鍵中產生重復值,那么就會發生舊行的更新;如果插入的行數據與現有表中記錄的唯一索引或者主鍵不重復,則執行新紀錄插入操作。

說通俗點就是數據庫中存在某個記錄時,執行這個語句會更新,而不存在這條記錄時,就會插入。

注意點:

  因為這是個插入語句,所以不能加where條件。

  如果是插入操作,受到影響行的值為1;如果更新操作,受到影響行的值為2;如果更新的數據和已有的數據一樣(就相當於沒變,所有值保持不變),受到影響的行的值為0。

該語句是基於唯一索引或主鍵使用,比如一個字段a被加上了unique index,並且表中已經存在了一條記錄值為1,

下面兩個語句會有相同的效果:

INSERT INTO table (a,b,c) VALUES (1,2,3)  
  ON DUPLICATE KEY UPDATE c=c+1;  
  
UPDATE table SET c=c+1 WHERE a=1;

ON DUPLICATE KEY UPDATE后面可以放多個字段,用英文逗號分割。

再現一個例子:

INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)  
      ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b); 

表中將更改(增加或修改)兩條記錄。

在mybatis中進行單個增加或修改sql的寫法為:

<insert id="insertOrUpdateCameraInfoByOne" paramerType="com.pojo.AreaInfo">
    insert into camera_info( cameraId,zone1Id,zone1Name,zone2Id,zone2Name,zone3Id,zone3Name,zone4Id,zone4Name)
    VALUES(
        #{cameraId},#{zone1Id},#{zone1Name}, #{zone2Id},
        #{zone2Name}, #{zone3Id}, #{zone3Name},
        #{zone4Id}, #{zone4Name},)
    ON DUPLICATE KEY UPDATE 
    cameraId = VALUES(cameraId),
    zone1Id = VALUES(zone1Id),zone1Name = VALUES(zone1Name),
    zone2Id = VALUES(zone2Id),zone2Name = VALUES(zone2Name),
    zone3Id = VALUES(zone3Id),zone3Name = VALUES(zone3Name),
    zone4Id = VALUES(zone4Id),zone4Name = VALUES(zone4Name)
</insert>

在mybatis中進行批量增加或修改的sql為:

<insert id="insertOrUpdateCameraInfoByBatch" parameterType="java.util.List">
      insert into camera_info(
          zone1Id,zone1Name,zone2Id,zone2Name,zone3Id,zone3Name,zone4Id,zone4Name,
          cameraId
          )VALUES
           <foreach collection ="list" item="cameraInfo" index= "index" separator =",">
             (
                #{cameraInfo.zone1Id}, #{cameraInfo.zone1Name}, #{cameraInfo.zone2Id},
                #{cameraInfo.zone2Name}, #{cameraInfo.zone3Id}, #{cameraInfo.zone3Name},
                #{cameraInfo.zone4Id}, #{cameraInfo.zone4Name}, 
                #{cameraInfo.cameraId}, 
             )
           </foreach>
           ON DUPLICATE KEY UPDATE
               zone1Id = VALUES(zone1Id),zone1Name = VALUES(zone1Name),zone2Id = VALUES(zone2Id),
               zone2Name = VALUES(zone2Name),zone3Id = VALUES(zone3Id),zone3Name = VALUES(zone3Name),
               zone4Id = VALUES(zone4Id),zone4Name = VALUES(zone4Name),
               cameraId = VALUES(cameraId)
    </insert>

項目中數據的操作有時候會令人頭大,遇到一個需求:

需要將數據從A數據庫的a數據表同步到B數據庫的b數據表中(ab表結構相同,但不是主從關系。。。just同步過去)

第一次同步過去,b表為空,同步很簡單。

但是當a表中的某些數據更新且增加了新數據之后,再想讓兩個表同步就有些麻煩了。(如果把b表清空,重新同步,數據量過大的話耗費的時間太長,不是一個好辦法)

想着能不能按照時間段來做更新,這段時間內有新數據了,就插入數據,有數據更新了就更新數據。先說下我的思路:

步驟:

  1.首先我從a表取出某一時間段的數據(分段更新)

  2.往b表內放數據,根據主鍵判斷b表是否已經有此條記錄,沒有此數據則插入,有了記錄則對比數據是否一樣,一樣則不做更改,不一樣就做更新操作。

此時使用該語句可以滿足需要,但是要注意幾個問題

  • 更新的內容中unique key或者primary key最好保證一個,不然不能保證語句執行正確(有任意一個unique key重復就會走更新,當然如果更新的語句中在表中也有重復校驗的字段,那么也不會更新成功而導致報錯,只有當該條語句沒有任何一個unique key重復才會插入新記錄);盡量不對存在多個唯一鍵的table使用該語句,避免可能導致數據錯亂。

  • 在有可能有並發事務執行的insert 語句情況下不使用該語句,可能導致產生death lock。

  • 如果數據表id是自動遞增的不建議使用該語句;id不連續,如果前面更新的比較多,新增的下一條會相應跳躍的更大。

  • 該語句是mysql獨有的語法,如果可能會設計到其他數據庫語言跨庫要謹慎使用。


主鍵不連續自增解決方法

源引自:https://www.linuxidc.com/Linux/2018-01/150427.htm

最近項目上需要實現這么一個功能:統計每個人每個軟件的使用時長,客戶端發過來消息,如果該用戶該軟件已經存在增更新使用時間,如果沒有則新添加一條記錄,代碼如下:

    <!-- 批量保存軟件使用時長表 -->
    <update  id="saveApp"  parameterType="java.util.List">
    <foreach collection="appList" item="item" index="index"  separator=";">
     insert into app_table(userName,app,duration)
     values(#{userName},#{item.app},#{item.duration})
     on duplicate key update duration=duration+#{item.duration}
  </foreach>
  </update>

為了效率用到了on duplicate key update進行自動判斷是更新還是新增,一段時間后發現該表的主鍵id(已設置為連續自增),不是連續的自增,總是跳躍的增加,這樣就造成id自增過快,已經快超過最大值了,通過查找資料發現,on duplicate key update有一個特性就是,每次是更新的情況下id也是會自增加1的,比如說現在id最大值的5,然后進行了一次更新操作,再進行一次插入操作時,id的值就變成了7而不是6.
為了解決這個問題,有兩種方式,第一種是修改innodb_autoinc_lock_mode中的模式,第二種是將語句修拆分為更新和操作2個動作

第一種方式:innodb_autoinc_lock_mode中有3中模式,0,1和2,mysql5的默認配置是1,

0是每次分配自增id的時候都會鎖表.

1只有在bulk insert的時候才會鎖表,簡單insert的時候只會使用一個light-weight mutex,比0的並發性能高

2.沒有仔細看,好像是很多的不保證...不太安全.

數據庫默認是1的情況下,就會發生上面的那種現象,每次使用insert into .. on duplicate key update 的時候都會把簡單自增id增加,不管是發生了insert還是update

由於該代碼數據量大,同時需要更新和添加的數據量多,不能使用將0模式,只能將數據庫代碼拆分成為更新和插入2個步驟,第一步先根據用戶名和軟件名更新使用時長,代碼如下:

  <update id="updateApp" parameterType="App">
   update app_table
   set duration=duration+#{duration}
   where userName=#{userName} and appName=#{appName}
  </update>

然后根據返回值,如果返回值大於0,說明更新成功不再需要插入數據,如果返回值小於0則需要進行插入該條數據,代碼如下:

<insert id="saveApp" keyProperty = "id" useGeneratedKeys = "true"  parameterType="App">
   insert into app_table(userName,appName,duration)
   values(#{userName},#{appName},#{duration})
  </insert>

產生death lock原理

insert ... on duplicate key 在執行時,innodb引擎會先判斷插入的行是否產生重復key錯誤,如果存在,在對該現有的行加上S(共享鎖)鎖,如果返回該行數據給mysql,然后mysql執行完duplicate后的update操作,然后對該記錄加上X(排他鎖),最后進行update寫入。

如果有兩個事務並發的執行同樣的語句,那么就會產生death lock,如:

 

 

 

 

參考文章:

https://www.cnblogs.com/zjdxr-up/p/8319982.html

https://blog.csdn.net/pml18710973036/article/details/78452688

 


免責聲明!

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



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