記一次數據庫死鎖追蹤過程

                 記一次數據庫死鎖追蹤過程

案例背景:從網上爬了一批數據,把用戶信息錄入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.在程序中判斷重複則忽略。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章