【mysql】 The last packet sent successfully to the server was 60,599,876 milliseconds ago

1. 引言

1.1 报错现象介绍

在Java开发过程中,你可能遇到了这样一个错误信息:

com.mysql.cj.jdbc.exceptions.CommunicationsException: The last packet successfully received from the server was 60,599,873 milliseconds ago. The last packet sent successfully to the server was 60,599,876 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.

这个错误信息来源于MySQL的JDBC驱动,它表示MySQL服务器在一段时间内没有收到来自客户端的任何数据包,超过了MySQL服务器设置的’wait_timeout’参数的值。这时,MySQL服务器会主动断开与客户端的连接,当客户端再次尝试使用这个已经被服务器断开的连接时,就会抛出上述错误。

1.2 报错的影响

这个报错可能会导致以下的问题:

  • 数据库操作失败:当你尝试使用已经断开的连接执行数据库操作时,操作会失败,可能会导致业务逻辑的执行中断。
  • 程序稳定性下降:如果你的程序没有正确处理这种异常,可能会导致程序崩溃或者进入不稳定状态。
  • 资源浪费:频繁的连接创建和断开会消耗额外的系统资源,可能会影响到其他程序的运行。

2. MySQL 'wait_timeout’参数

2.1 'wait_timeout’参数的含义

MySQL的’wait_timeout’参数定义了服务器在关闭非交互式连接前等待活动的秒数。非交互式连接指的是对于命令行客户端,除非在启动时明确指定–interactive-timeout选项,否则其超时值将由’wait_timeout’变量决定。如果在这个时间内,服务器没有收到来自客户端的任何数据,它就会自动关闭连接。

这个参数的默认值可能会根据MySQL的版本和配置有所不同,但通常在几小时到几天之间。

2.2 如何查看和修改’wait_timeout’参数

查看’wait_timeout’参数的值,可以在mysql客户端执行以下命令:

SHOW VARIABLES LIKE 'wait_timeout';

修改’wait_timeout’参数的值,可以在mysql客户端执行以下命令:

SET GLOBAL wait_timeout = time_in_seconds;

其中,time_in_seconds是你想要设置的新的超时时间,单位为秒。

请注意,如果你想要永久修改这个参数的值,你需要在MySQL的配置文件(通常是my.cnfmy.ini)中进行设置,并重启MySQL服务器。在配置文件中,你可以在[mysqld]部分添加或修改以下行:

wait_timeout = time_in_seconds

同样,time_in_seconds是你想要设置的新的超时时间,单位为秒。

3. Java代码中的MySQL连接

3.1 Java中如何建立MySQL连接

在Java中,我们通常使用JDBC(Java Database Connectivity)API来连接MySQL数据库。以下是一个简单的示例:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class TestConnection {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/testdb";
        String username = "testuser";
        String password = "testpassword";

        try {
            Connection connection = DriverManager.getConnection(url, username, password);
            System.out.println("Connected to the database.");
            // do something with the connection...
            connection.close();
        } catch (SQLException e) {
            System.out.println("Cannot connect to the database.");
            e.printStackTrace();
        }
    }
}

在这个例子中,我们首先导入了java.sql.Connectionjava.sql.DriverManager类。然后,我们定义了数据库的URL、用户名和密码。接着,我们使用DriverManager.getConnection()方法来创建一个新的数据库连接。

3.2 Java中MySQL连接的生命周期

在Java程序中,一个数据库连接的生命周期通常如下:

  1. 创建连接:当你调用DriverManager.getConnection()方法时,会创建一个新的数据库连接。
  2. 使用连接:一旦连接被创建,你就可以使用它来执行SQL查询和更新。你可以通过Connection对象的createStatement()prepareStatement()方法来创建一个StatementPreparedStatement对象,然后使用这个对象来执行SQL命令。
  3. 关闭连接:当你完成了所有的数据库操作后,你应该调用Connection.close()方法来关闭连接。这是非常重要的,因为如果你不关闭连接,那么这个连接可能会一直保持打开状态,直到程序结束。这可能会消耗大量的系统资源,并且可能会导致数据库服务器达到最大连接数限制。

需要注意的是,如果你的程序使用了连接池(例如,Apache DBCP或C3P0),那么连接的生命周期可能会有所不同。在这种情况下,当你调用Connection.close()方法时,连接通常不会真的被关闭,而是被返回到连接池中,以便后续再次使用。

4 问题复现

4.1 示例代码:模拟长时间无交互导致的连接断开

在下面的Java代码示例中,我们创建了一个MySQL连接,然后让线程休眠一段时间(例如,超过wait_timeout参数设置的时间),然后再试图执行一个SQL语句。这将模拟长时间无交互导致的连接断开的情况。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TestConnection {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/testdb";
        String username = "testuser";
        String password = "testpassword";

        try {
            Connection connection = DriverManager.getConnection(url, username, password);
            System.out.println("Connected to the database.");

            // Sleep for a while to simulate inactivity
            Thread.sleep(10000);  // adjust this value as needed

            // Now try to do something with the connection
            Statement stmt = connection.createStatement();
            stmt.executeQuery("SELECT 1");
            System.out.println("Query executed.");

            connection.close();
        } catch (SQLException e) {
            System.out.println("Cannot connect to the database.");
            e.printStackTrace();
        } catch (InterruptedException e) {
            System.out.println("Thread was interrupted.");
            e.printStackTrace();
        }
    }
}

4.2 报错解读

如果在休眠期间MySQL服务器关闭了连接,当我们试图使用这个连接执行SQL语句时,我们可能会收到一个SQLException。这个异常的消息可能类似于"Communications link failure"或"The last packet successfully received from the server was XXX milliseconds ago.",这表明连接已经被断开。

为了解决这个问题,我们可以:

  1. 确保我们的程序定期使用数据库连接,以防止它们因为长时间的不活动而被断开。
  2. 在数据库服务器上增加wait_timeout参数的值,以允许连接保持更长时间的不活动状态。
  3. 在我们的程序中添加逻辑来检测和处理断开的连接。例如,我们可以在执行SQL语句之前检查连接是否仍然有效,如果不是,我们可以创建一个新的连接。

5. 问题解决方案

方案一:调整MySQL的’wait_timeout’参数

你可以通过调整MySQL的’wait_timeout’参数来解决这个问题。'wait_timeout’参数定义了非交互式连接(如Java应用程序到MySQL服务器的连接)在空闲状态下的最长等待时间。当连接空闲时间超过这个参数的值时,MySQL服务器将自动关闭该连接。

操作步骤:

  1. 登录MySQL服务器
  2. 运行以下命令查看当前的’wait_timeout’参数值:
SHOW VARIABLES LIKE 'wait_timeout';
  1. 运行以下命令设置’wait_timeout’参数值(例如,设置为8小时):
SET GLOBAL wait_timeout=28800;

注意: 这种方法的缺点是需要直接修改MySQL服务器的配置,可能需要数据库管理员的权限。

方案二:在Java代码中使用连接池管理MySQL连接

连接池可以有效地管理和复用数据库连接,避免了频繁地创建和关闭连接,同时可以防止连接空闲过久被MySQL服务器自动关闭。在大部分项目中都是用连接池管理的,不用关注该方案

如何使用连接池:

以下是使用HikariCP连接池的示例代码:

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("password");
config.addDataSourceProperty("autoReconnect", "true");

HikariDataSource ds = new HikariDataSource(config);

// 使用完后记得关闭
ds.close();
方案三:在Java代码中设置’autoReconnect=true’

将’autoReconnect=true’添加到JDBC连接字符串中,可以使得在连接被MySQL服务器自动关闭后,Java应用程序可以自动重新连接。在大部分项目中都是用连接池管理的,不用关注该方案

如何设置’autoReconnect=true’:

以下是在JDBC连接字符串中设置’autoReconnect=true’的示例代码:

String url = "jdbc:mysql://localhost:3306/test?autoReconnect=true";
Connection connection = DriverManager.getConnection(url, "root", "password");
方案四:使用声明式事务管理(@Transactional注解)

在方法上使用@Transactional注解,Spring会在该方法执行前自动开始一个新的事务,并在该方法执行后自动提交或回滚事务。

示例代码:

import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void updateUser(User user) {
        userRepository.save(user);
    }
}
方案五:使用编程式事务管理(transactionManager.getTransaction()方法)

当你调用transactionManager.getTransaction()方法时,你需要显式地开始、提交或回滚事务。
这种方式经过实践 如果是在循环中创建编程时事务 还是有可能出现标题中的报错 所以如果有循环处理场景推荐使用方案四

示例代码:

import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Service
public class UserService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private UserRepository userRepository;

    public void updateUser(User user) {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            userRepository.save(user);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}
方案六:检查网络

在网上翻阅其他人的解决方案时候,发现网络问题也可能导致这个报错
https://zhuanlan.zhihu.com/p/550145054

方案七:修改springboot中的超时时间

在网上翻阅其他人的解决方案时候,发现也可以通过修改java应用中的连接池配置来实现 不同的数据库连接池配置名可能略有不同
https://blog.csdn.net/weixin_45204847/article/details/112282739

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值