记一次数据库死锁追踪过程
记一次数据库死锁追踪过程
案例背景:从网上爬了一批数据,把用户信息录入mysql数据库中了,但是后面爬取到的信息需要根据用户名和id去更新。数据量100w+,要修改的数据量30w+.
业务逻辑代码描述
1.访问数据库获取id,name,放入map中,key=name,value=id
2.访问xxx.txt获取用户名,粉丝数量,关注数量放入list中,起事务
3.循环list,在list中循环访问数据库根据id获取记录数,然后更新该记录
4.结束事务。
前奏:
总结:这种方案在数据量小的时候运行还可以,现在有100w+数据量,同时在for循环中访问数据库,导致的结果就是运行时内存不断增加,循环后期fgc从每30秒一次到每秒一次,即使是显示的设置已处理过的entity对象为null,也解决不了问题,同时将map中的数据清除也不行。
改进:
由于我的数据库连接是自己开发的,持久化层也是自己开发的,所以就加了一个直接拼接update sql来解决返回对象过大的问题。将步骤三改为:
循环list,在list中循环生成kv对象,k是columnName,v是值,然后把id一起传入daoclient(自己开发的简单dao组件)方法中。
Sql: update user set kname=vname,kfans=vfans,kfocus=vfocus where id =vid;
为了使用Java8的特性,将list通过stream并行的方式去处理每条记录,但是这出现了另一个问题,数据库链接最大设置的5不够用了,底层使用的list存储每个链接,经过改进使用LinkedBlockingDeque存储链接就不会出现不够用的情况了,由于是并行访问数据库,所以链接不够用的情况还可能会存在。
高潮:
根据上面描述的过程可能大牛们和菜鸟们都知道一些原因和原理,那现在为啥出现死锁呢,异常如下:
java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1055)
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)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1936)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2060)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2542)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1734)
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:995)
at com.coderman.daoclient.connections.StatementTask.realExeUpdateSql(StatementTask.java:446)
at com.coderman.daoclient.connections.StatementTask.exeUpdate(StatementTask.java:389)
at com.coderman.daoclient.dao.impl.IDaoImpl.updateEntity(IDaoImpl.java:56)
at com.coderman.daotest.dao.IDaoImplTest.lambda$main$0(IDaoImplTest.java:59)
at com.coderman.daotest.dao.IDaoImplTest$$Lambda$1/451111351.accept(Unknown Source)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1689)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
一开始以为是自己的daoclient组件的问题,但是根据异常代码提示显示应该不是dao的问题。
开始百度:
1.找到MySQL关于死锁的命令一一重试:
show full processlist;发现3个update语句在等待执行。
没有发现问题,经过其他提示,发现只要有不同sql或者不同的程序访同问一张表就可能出现死锁。于是使用下面的命令:
SELECT * FROM information_schema.innodb_trx
查出确实是上面三个update语句的问题,那为什么update语句会造成死锁呢,这里大概都知道原因了吧。
总结:因为xxx.txt文件里有重复的数据那三条update语句本来应该执行了,但是后面有出现相同的update语句,针对的是同一行,使用了事务,之前提交的update语句还没有完成,后面又有相同行的update请求,所以导致死锁了。
在循环中出现死锁之后,其他行的update就变慢了,性能急速下降。。。
尾声:
解决方案:
1.手动将重复的三条记录删除
2.在程序中使用set集合过滤
3.在程序中判断重复则忽略。
上一篇: List转tree的方法,支持数据量很大的情况(效率对比)
下一篇: 推荐阅读