mysql报错 DuplicateKeyException分析与解决
在做數據庫同步的時候,發現一個錯誤,mysql報錯如下:
org.springframework.dao.DuplicateKeyException: ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:XXX ### The error may involve com.jd.medicine.b2c.trade.center.daoHistory.RxOrderHistoryDao.addRxOrder-Inline ### The error occurred while setting parameters ### SQL: INSERT INTO XXX (... ) VALUES ( ? ,? ,? ) ### Cause: error: code = AlreadyExists desc = Duplicate entry 'XXXXX' for key 'PRIMARY' (errno 1062) (sqlstate 23000) during query: insert into XXX( ...) values (...); at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:245) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:71) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:364) at com.sun.proxy.$Proxy17.insert(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:236) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:46) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:43) at com.sun.proxy.$Proxy25.addRxOrder(Unknown Source) ... ... 49 common frames omitted根據報錯信息得知,這個錯誤是因為相同主鍵重復插入導致的
想到我的業務邏輯是:實時同步數據庫(A庫主數據,實時同步到B庫)
1.實時接收A庫的變化,一有變化就接收binlog;
2.根據binlog的主鍵,先查詢B庫有沒有這條數據,有就修改,沒有就插入,
問題就在第二步,如果并發極高,兩條相同的binlog同時過來,
第一條來了先查詢B庫,沒有發現這條數據,執行插入操作,正常,
就在此時,第二條binlog也來了,此時第一條還沒有插入成功,所以此時查詢B庫,結果還是沒有這條數據,
然后就執行插入操作,
此時其實B庫中是已經插入這條數據的,所以第二次插入就會報錯,相同主鍵重復插入.
解決思路:
方案1:
通過業務端,將并發量減少就可以了,
比如已知一條數據插入的時間間隔是10-20ms;那么就在插入前的查詢判斷,讓線程sleep一個隨機的時間(20ms<time<30ms),
我們知道,讓線程sleep,會增加cup的使用,如果cpu比較緊張,這并不是一個很好的方法,
但是如果你的系統可以動態的增加機器,那么線程sleep就不是什么問題了.
方案2:
每次插入的時候,在redis中緩存一下,設置過期時間,比如說設置成1秒,
然后每次插入前都查一下redis,如果能查到值,那么就證明本條數據是插入過的,這樣也可以防重
但是這樣會引入第三方redis,這個就是分布式常說的問題了,還要考慮redis宕機的情況
方案3:
使用mysql的函數,下面這段是在網上找的,基本思路是讓mysql自己消化這種問題,僅供參考:
1.insert ignore into 當插入數據時,如出現錯誤時,如重復數據,將不返回錯誤,只以警告形式返回。所以使用ignore請確保語句本身沒有問題,否則也會被忽略掉。例如: INSERT IGNORE INTO books (name) VALUES ('MySQL Manual') 這種方法很簡便,但是有一種可能,就是加入不是因為重復數據報錯,而是因為其他原因報錯的,也同樣被忽略了~2.on duplicate key update 當primary或者unique重復時,則執行update語句,如update后為無用語句,如id=id,則同1功能相同,但錯誤不會被忽略掉。例如,為了實現name重復的數據插入不報錯,可使用一下語句: INSERT INTO books (name) VALUES ('MySQL Manual') ON duplicate KEY UPDATE id = id 這種方法有個前提條件,就是,需要插入的約束,需要是主鍵或者唯一約束(在你的業務中那個要作為唯一的判斷就將那個字段設置為唯一約束也就是unique key)。3.insert … select … where not exist 根據select的條件判斷是否插入,可以不光通過primary 和unique來判斷,也可通過其它條件。例如: INSERT INTO books (name) SELECT 'MySQL Manual' FROM dual WHERE NOT EXISTS (SELECT id FROM books WHERE id = 1) 這種方法其實就是使用了mysql的一個臨時表的方式,但是里面使用到了子查詢,效率也會有一點點影響,如果能使用上面的就不使用這個。4.replace into 如果存在primary or unique相同的記錄,則先刪除掉。再插入新記錄。 REPLACE INTO books SELECT 1, 'MySQL Manual' FROM books 這種方法就是不管原來有沒有相同的記錄,都會先刪除掉然后再插入。我最終的解決方法
由于我的項目這個操作是通過消費mq消息來insert的.那么就算是報錯,mq也會重試的,
下次重試的時候,就可以查到B庫是有數據的,所以就正常處理了,也沒有報錯了,
所以即使我不處理,也不會影響系統數據,
之所以把這個拋出來,是因為系統對sql設置了報警,我是不想讓這種情況一直報警
考慮到要盡量少的依賴redis等第三方,所以方案2pass掉了
業務很有可能變化.如果在業務上做太多判斷,以后更改業務就會無意的在這塊留下坑,所以方案1也pass.
最終用了方案3的第一種,insert ignore into
如果確實是ignore了,業務返回是0,將這種情況特殊處理,比如重新操作一遍,就不會有上述問題了
至此,轉了一大圈,問題解決!
總結
以上是生活随笔為你收集整理的mysql报错 DuplicateKeyException分析与解决的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [spring boot] ------
- 下一篇: 两个numpy取相同值_闲谈Numpy的