在實際應用中,經常碰到導入數據的功能,當導入的數據不存在時則進行添加,有修改時則進行更新,
在剛碰到的時候,一般思路是將其實現分為兩塊,分別是判斷增加,判斷更新,后來發現在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