一个线上系统,稳定运行了一段时间后突然出现一些死锁情况,程序抛异常类似于下面这样:
- com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
- at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
- at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
- at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
- at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
- at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
- at com.mysql.jdbc.Util.getInstance(Util.java:381)
- at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1045)
- at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
- at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)
- at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)
导致出现问题的SQL是这样的
- insert into xxx (...)
- select aaa, bbb from dual where not exists (
- select * from xxx where aaa = yyy
- )
这是一个我们很常见的场景:先查下某条数据是否存在,若不存在就插入一条。
之所以sql这样写,是为了防止高并发情况下发生唯一性约束冲突的异常,这种先select再insert的写法,被称作conditional insert。这里有一个潜在问题就是内层select会加锁,如果此时另外一个线程也进行查询操作,会直接deadlock。
解决办法也很粗暴:
直接insert,不带任何条件,然后程序里面做下容错,将所有唯一性约束的异常吃掉。
MySQL里面有很多这样看上去很美的写法和功能,真正用了才发现自己掉坑里了。生产环境,还是保守一点好。