解决Java从MySQL读取大量数据时卡在SocketInputStream.socketRead0的问题

今天晚上突然有个服务无法启动。这个服务在启动的时候会从数据库中加载一些数据。查看日志:有开始加载的日志,但没有完成加载的日志,判断问题是在加载数据时卡住。用top查看进程,发现CPU占用为0%。又怀疑可能是因为jvm内存不够,用jstat查看jvm内存使用情况,发现各区内存占用率较低,连young gc都没有出现。再用jstack查看线程栈,发现线程卡在JDBC底层的TCP套接字读取上:
 ------------------------------------------------------------------------
main prio=10 tid=0x000000004cf54800 nid=0x1ec4 runnable [0x00002b990a821000]
   java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:113)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:160)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:188)
- locked <0x00000007da4b7df8> (a com.mysql.jdbc.util.ReadAheadInputStream)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1953)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2421)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2867)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:870)
at com.mysql.jdbc.MysqlIO.nextRow(MysqlIO.java:1345)
at com.mysql.jdbc.MysqlIO.readSingleRowSet(MysqlIO.java:2326)
at com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:436)
at com.mysql.jdbc.MysqlIO.readResultsForQueryOrUpdate(MysqlIO.java:2033)
at com.mysql.jdbc.MysqlIO.readAllResults(MysqlIO.java:1436)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1770)
--------------------------------------------------------------------------------
     开始进行搜索,看到有种说法说是由于读取的数据量太大导致。一开始我不相信是这个原因。因为读取的表也就不到1万条的数据,每条记录尺寸也不大,合在一起还不到1MB的数据。但是我测试发现:如果将表的大部分数据删除,则服务就能加载成功。还真的是这个原因。从搜索到的结果数量来看,遇到这个问题的人不多,可能与我们JVM(1.6.29)和MySQL版本(5.0.77)比较老旧有关系。
    继续搜索解决方案,找到一个解决方案是用limit限制返回条目数,这个解决方案比较麻烦,需要修改代码,把从一次加载整张表的数据改成分多次进行加载。所以我没有立即采用。
    后面看到有人说通过jdbc url的tcpRcvBuf属性设置可以解决这个问题。想了想感觉有可能,于是跑去看MySQL官方文档的相关章节,《Driver/Datasource Class Names, URL Syntax and Configuration Properties for Connector/J》。tcpRcvBuf的说明是:connecting using TCP/IP, should the driver set SO_RCV_BUF to the given value? The default value of '0', means use the platform default value for this property)。
    我尝试将TCP接收缓冲区增大到1MB,jdbc:mysql://127.0.0.1:3306/ooxx?characterEncoding=utf-8&tcpRcvBuf=1024000。启动服务,顺利加载成功了。
    虽然现在说得好像挺轻松的,但今天解决这个问题也花了我两个多小时的时间,各种尝试,各种走弯路,差点准备改代码了。所以在这边记录下来,如果能节省后来人的时间那就太好了。
展开阅读全文

没有更多推荐了,返回首页