mysql报错 DuplicateKeyException分析与解决

mysql报错 DuplicateKeyException分析与解决在做数据库同步的时候,发现一个错误,mysql报错如下:org.springframework.dao.DuplicateKeyException:###Errorupdatingdatabase.Cause:com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:XXX###Thee…

mysql报错

在做数据库同步的时候,发现一个错误,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分析与解决分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/33097.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注