封面图片

编程

聊聊MySQL的wait_timeout

问题

在之前开发的数据同步系统中,使用了dataX对于一个将Hive数据导入到MySQL的操作在最后一步报了一个错误,大概的意思是,从数据库获得的最后一个数据包是什么时间之前,这个时间超过了数据库设置的wait_timeout。 这是报错的信息:

12024-03-25 01:18:46.220 WARN [job-6459] com.zaxxer.hikari.pool.ProxyConnection Line:182 - HikariPool-1 - Connection com.mysql.cj.jdbc.ConnectionImpl@2f456ee0 marked as broken because of SQLSTATE(08S01), ErrorCode(0) 2com.mysql.cj.jdbc.exceptions.CommunicationsException: The last packet successfully received from the server was 621,927 milliseconds ago. The last packet sent successfully to the server was 621,927 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. 3 at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174) 4 at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64) 5 at com.mysql.cj.jdbc.ConnectionImpl.setReadOnlyInternal(ConnectionImpl.java:2150) 6 at com.mysql.cj.jdbc.ConnectionImpl.setReadOnly(ConnectionImpl.java:2134) 7 at com.zaxxer.hikari.pool.ProxyConnection.setReadOnly(ProxyConnection.java:423) 8 at com.zaxxer.hikari.pool.HikariProxyConnection.setReadOnly(HikariProxyConnection.java) 9 at org.springframework.jdbc.datasource.DataSourceUtils.prepareConnectionForTransaction(DataSourceUtils.java:188) 10 at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:153) 11 at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:421) 12 at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400) 13 at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)

分析

问题代码示例:

1public void run() { 2 // 数据库操作 3 repo.save() 4 // 耗时任务,运行时间超过10分钟 5 doSomeTask() 6 //数据库操作,也就是出错的地方 7 repo.update() 8}

我通过命令看了一下数据库的global wait_timeout,时间是300秒,小于任务的执行时间,导致报错。 该任务是先更新数据库一条记录,记作操作1,然后执行一个比较耗时的方法,记作操作2,之后再更新同一条数据库记录,记作操作3。就是因为中间这个操作2执行时间长,导致在执行操作3之前,数据库已经根据global wait_timeout关闭了这个连接,导致操作3执行失败报错。 解决的办法是将global wait_timeout改成8个小时。

2种wait_timeout

wait_timeout分为session 和 global,每种使用的场景不同。 出现问题的数据库的session wait_timeout是28800秒,也就是8个小时,global wait_timeout是5分钟。这2个配置各是什么作用呢? 这里从AI 助手kimi获得的解释:

global wait_timeout和session wait_timeout是MySQL数据库中用于控制连接空闲超时的两个参数,它们的主要区别在于作用范围和配置级别的不同。

  1. 作用范围
    • global wait_timeout:这个参数是全局级别的,影响所有新的连接以及尚未创建的会话。它定义了非交互式连接(如通过程序连接,例如使用JDBC等)在服务器关闭之前等待的秒数。
    • session wait_timeout:这个参数是会话级别的,只影响当前会话。它定义了交互式连接(如通过MySQL客户端连接)在服务器关闭之前等待的秒数。
  2. 配置级别
    • global wait_timeout:可以在MySQL的配置文件(如my.cnf或my.ini)中设置,或者在MySQL命令行中使用SET GLOBAL语句进行动态设置。
    • session wait_timeout:可以在会话级别使用SET语句进行设置,这样就只会影响当前用户的当前会话。
  3. 继承关系
    • 在交互模式下,session级别的wait_timeout会继承global级别的interactive_timeout的值。
    • 在非交互模式下,session级别的wait_timeout会继承global级别的wait_timeout的值。
  4. 影响
    • 当global wait_timeout设置后,所有新的会话在启动时会继承这个值作为其session wait_timeout的初始值。
    • session wait_timeout的更改只会影响当前会话,不会影响其他会话或全局设置。

在实际应用中,根据需要对这两个参数进行适当的配置,可以有效地管理数据库连接,避免长时间空闲的连接占用资源。例如,如果你的应用程序主要是通过程序进行数据库操作,那么可能需要关注并调整global wait_timeout;如果你需要为特定的用户会话设置不同的超时时间,那么可以在会话级别调整session wait_timeout。

也就是说,global 类型的是非交互式的,影响的一般是程序连接,也就是本次报错的原因所在。 session类型的是交互式连接,一般影响客户端,比如Navicat的连接方式。 默认设置一般都是8小时,也可以针对不同情况进行优化调整。

延伸

为什么使用了数据库连接池还会出现连接断开的情况?

建立连接是比较耗费性能的,所以连接池的出现就是为了维护一批建立好的连接。 这些建立的连接也会受到客户端和服务端(MySQL)配置参数的影响。 连接池中维护的连接也会断开。 但是为什么我们在正常跟数据库进行交互的时候,不会出现问题? 主要是因为连接池在把连接给到当前线程时,会提前判断该连接是否正常,如果正常则直接把连接给当前线程,如果连接测试失败,则创建一个新连接给当前线程。所以当前线程一直可以用到正常的连接。 出现连接被断开的情况的原因是,线程拿到了连接池给的正常的连接,我们的线程在执行耗时的任务,时间超过了设置的wait_timeout的时间,导致MySQL服务器关闭了这个连接。 常见的连接池有HicariCPDruidHicariCP性能更好,Druid更全面,监控更好。

如何避免连接被断开?

因为我们知道了连接被断开的原因主要是当前持有连接的线程执行时间过长导致的。 避免连接被断开的方式可以是:

  1. 优化线程的执行时长
  2. 使用异步的方式,将任务执行的结果通过事件机制通知给另一个线程,新线程负责将结果持久化到数据库。

其他相似的情况

web请求是客户端浏览器向后端服务发送http请求。有些请求耗时长,超过了请求的超时限制,客户端就会主动断开连接,而服务器还在处理请求,当请求处理完了之后,服务端发现连接已经断开了。这个时候查看服务器的连接信息,就会有很多处于close_wait的状态。当这种状态的连接越来越多,就会严重占用服务器资源,影响服务器性能。 那么对于这类问题的处理办法,需要根据实际场景来决定。 比如需要及时返回的数据,那么就要优化整个后端的处理流程,确定耗时的操作是否是IO导致的,还是本身代码写的有问题,使用数据库索引,使用缓存等。 如果是对于像触发执行任务一类的功能,可以直接返回结果,后端启用新的线程去处理任务,在暴露一个接口提供任务状态的查询。

2024年03月29日
在初学者眼中,世界充满了可能;专家眼中,世界大都已经既定。--铃木俊隆