在實際應用中,經常碰到導入數據的功能,當導入的數據不存在時則進行添加,有修改時則進行更新,
在剛碰到的時候,一般思路是將其實現分為兩塊,分別是判斷增加,判斷更新,后來發現在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);
表中將更改(增加或修改)兩條記錄。
項目中數據的操作有時候會令人頭大,遇到一個需求:
需要將數據從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獨有的語法,如果可能會設計到其他數據庫語言跨庫要謹慎使用。
產生death lock原理
insert ... on duplicate key 在執行時,innodb引擎會先判斷插入的行是否產生重復key錯誤,如果存在,在對該現有的行加上S(共享鎖)鎖,如果返回該行數據給mysql,然后mysql執行完duplicate后的update操作,然后對該記錄加上X(排他鎖),最后進行update寫入。
如果有兩個事務並發的執行同樣的語句,那么就會產生death lock,如: