我遇到了一些奇怪的行为。我有一个Java程序,它使用预准备语句和批量插入将数据永久写入MySQL表。如果某个其他进程在同一个表上发出LOCK TABLES t WRITE
,并在此后不久发布(几分钟后),Java程序将按预期继续。但是,当锁定持续较长时间(超过30分钟)时,Java程序将断开连接并且不会继续。 2小时后,它失败并显示通讯链接失败。表锁定期间挂起的insert语句在锁定释放后执行,但之后连接消失。
以下是详细信息:
wait_timeout
设置为28800(8小时)有谁知道这里发生了什么?我需要设置/增加超时吗?
两小时后抛出的异常:
Exception in thread "main" java.sql.BatchUpdateException: Communications link failure
The last packet successfully received from the server was 7.260.436 milliseconds ago. The last packet sent successfully to the server was 7.210.431 milliseconds ago.
at com.mysql.jdbc.PreparedStatement.executeBatchedInserts(PreparedStatement.java:1836)
at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1456)
at com.mysql.jdbc.CallableStatement.executeBatch(CallableStatement.java:2499)
at main.LockTest.main(LockTest.java:26)
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 7.260.436 milliseconds ago. The last packet sent successfully to the server was 7.210.431 milliseconds ago.
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:411)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1121)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3670)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3559)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4110)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2570)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2731)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2815)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2458)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2375)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2359)
at com.mysql.jdbc.PreparedStatement.executeBatchedInserts(PreparedStatement.java:1792)
... 3 more
Caused by: java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:114)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:161)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3116)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3570)
... 13 more
完整的Java程序:
package main;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class LockTest {
private static final String CONNECTION_PATTERN = "jdbc:mysql://%s/?user=%s&password=%s"
+ "&autoReconnect=true&rewriteBatchedStatements=true&characterEncoding=utf8";
private static final String QUERY = "INSERT INTO test.lock_test (random_text) VALUES (?)";
public static void main(String[] args) throws SQLException, InterruptedException {
Connection con = DriverManager.getConnection(String.format(CONNECTION_PATTERN, "host", "user", "pw"));
PreparedStatement ps = con.prepareCall(QUERY);
int i = 0;
while (true) {
i++;
ps.setString(1, Long.toString(System.currentTimeMillis()));
ps.addBatch();
if (i % 10 == 0) {
ps.executeBatch();
System.out.println(new Date() + ": inserting 10 rows");
}
Thread.sleep(5000);
}
}
}
答案 0 :(得分:3)
最后,我发现了真正的原因。 MySQL的wait_timeout
与问题无关 - 只要语句正在运行(无论多长时间),数据库会话就不在状态SLEEP
中,因此会话永远不会被关闭通过MySQL。
原因似乎是Linux操作系统(Debian 6或7),它在闲置2小时后关闭了tcp连接(我不知道具体的机制,也许有人可以详细说明这个?)。
为了避免所描述的超时,需要发送更频繁的tcp keep alive包。要做到这一点,必须减少/proc/sys/net/ipv4/tcp_keepalive_time
(我将它从7,200减少到60)。 cat 60 > /proc/sys/net/ipv4/tcp_keepalive_time
暂时这样做;要在系统重新启动后保留它,必须在net.ipv4.tcp_keepalive_time=60
中设置/etc/sysctl.conf
。
答案 1 :(得分:0)
我只是搜索同一个问题。我想我的回答可以帮到你。
从服务器成功收到的最后一个数据包是7.260.436毫秒之前。
这显然意味着7.260.436毫秒< wait_timeout设置为28800
2.如果您的应用程序抛出异常,因为wait_timeout太小。例外显示如下:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 46,208,149 milliseconds ago. The last packet sent successfully to the server was 46,208,150 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem. ; SQL []; The last packet successfully received from the server was 46,208,149 milliseconds ago. The last packet sent successfully to the server was 46,208,150 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.;
这意味着46,208,150毫秒之前> WAIT_TIMEOUT
注意以下关键词"通信链路故障" ,这主要是关于MySQl变量: net_write_timeout或net_read_timeout 。
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
net_write_timeout和net_read_timeout的默认值很小,可以执行
mysql> show variables like '%timeout%';找到结果。