生产系统 SQL 执行异常原因分析

起因

最近一段时间,生产系统持续碰到一些数据库异常,导致 sql 执行失败。

应用环境

Java 1.7 + Mysql 5.6 + spring + ibatis

问题排查

将各种失败的异常记录了一下,碰到最多下面几种异常。

  1. java.net.SocketTimeoutException: Read timed out
  2. java.sql.BatchUpdateException: No operations allowed after statement closed。
  3. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
    • java.io.EOFException: Can not read response from server. Expected to read 8 bytes, read 7 bytes before connection was unexpectedly lost.
    • java.net.SocketException: Software caused connection abort: recv failed

SocketTimeoutException

针对上面第一种情况,很容易从字面意义就得出是读取超时。然而查询资料 JDBC 存在多种 timeout,仔细研究了一下,梳理一下。

JBDC 可以设置超时时间分别是 Transaction Timeout,Statement Timeout,Socket TimeOut,ConnectionTimeout。上述超时时间层次从上至下。

以下我们从上之下分别了解这几种种超时时间。

Transaction Timeout :事务超时时间,由多个 Statement 组成。事务的超时时间=N*Statement.timeout+其他代码执行时间。所以我们不应该在一个事务中执行一些 RPC 或 HTTP 等这些长耗时的调用。如果时间卡在这些调用上,会导致事务超时发生回滚。

Statement Timeout:一次语句的执行的时间,可以用来限制一个查询语句的执行时间。但是如果出现网络故障,这个超时间将不起作用。最终需要 Socket TimeOut 解决。

Socket TimeOut :目前 JDBC 类型存在四种,而我们通常使用的是数据库协议驱动(Database-Protocol driver (Pure Java driver) or thin driver)。这种驱动采用 Socket 用来与数据库通信。若没有设置,一但发生网络故障,SCOKET 读取就会直接阻塞。而设置以后,时间超时后将会抛出 java.net.SocketTimeoutException: Read timed out,防止长时间阻塞,系统不可用。

ConnectionTimeout :这个超时参数也是与 Socket 建立连接有关。若没有设置,一旦如果数据库相关地址参数错误错误,将会长时间阻塞在建立数据库连接上。

使用网上一张图可以清晰的解析前三者关系。

实际上还存在操作系统层面上 Socket 超时。各个操作系统可以设置相应 Socket 超时时间,然后若 JDBC 没有设置,到了操作系统的超时时间也将会断开。但是我们不能依赖该超时间,因为该时间完全不可控,我们应该显式设置。

综上,针对相关 JDBC 参数我们至少需要设置 ConnectionTimeout 以及 Socket TimeOut.针对 sql 语句,可以设置 Statement Timeout。若存在事务,还可以设置相应 Transaction Timeout。

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException

这个 CommunicationsException 异常会因为其他底层异常导致如以下这两种异常。

  1. java.io.EOFException: Can not read response from server. Expected to read 8 bytes, read 7 bytes before connection was unexpectedly lost.
  2. java.net.SocketException: Software caused connection abort: recv failed

刚开始碰到该异常,根据 CommunicationsException 查询一下了,大致都是说 Mysql server 端会检测空闲连接,超时后主动断开连接,导致客户端的连接失效。

那么什么是 mysql 的空闲连接那?简单来说,mysql 连接进程 Command 为 sleep 状态。我们可以使用 show processlist ; 查看正在运行的进程。空闲的进程示例如图:

jdbc 连接会根据 mysql wait_timeout 检测空闲连接。若在 wait_timeout 时间内,连接还是空闲状态,mysql server 将会断开这个链接。针对这种情况,采用编码模拟。 采用如下代码:

		try {
			Connection connection = dataSource.getConnection();
			TimeUnit.SECONDS.sleep(11L);
			run.query(connection,"select 'X'", h);
			//Thread.sleep(60000);
		} catch (Exception e) {
			log.error("查询异常", e);
		}
复制代码

然后设置 mysql wait_timeout=10 。 以下模拟代码获取连接后,休眠11s,这个过程中,mysql 主动断开连接,等真正执行时,程序抛出异常。

以下为报错的情况:

但是底层异常却为 java.net.SocketException: Software caused connection abort: recv failed,而不是 java.io.EOFException。

这个报错却是很疑惑。然后仔细查看 EOFException 后面描述 Expected to read 8 bytes, read 7 bytes before connection was unexpectedly lost,可以看出这个连接其实有一段时间其实还是可用,有读取数据,但是在读取数据过程中,未读到符合数量的相应数据,导致报错。而上面代码模拟的却是连接使用时连接已生效的情况。

执行 show variables like '%timeout%'; 查看 mysql 其他超时时间,

从上图可以注意到 net_read_timeoutnet_write_timeout 这两个参数。

查看 mysql 官方文档

net_read_timeout 默认30s The number of seconds to wait for more data from a connection before aborting the read. When the server is reading from the client, net_read_timeout is the timeout value controlling when to abort. When the server is writing to the client, net_write_timeout 默认60s The number of seconds to wait for a block to be written to a connection before aborting the write

net_write_timeout 控制 mysql 服务端向客户端写数据超时时间。针对这种情况,在 MysqlIO read 处打上短点,

程序启动时,先放开断点,查看 mysql processlist,看到 mysql 进程 state send to client 时,这个时候使断点生效。这个时候,等待60s 以后,成功复现出如下错误。

net_read_timeout 该超时不知道如何模拟:(。

综上,若发生 com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure 异常,为数据库连接失效,但是失效的原因可能会有多种,大致都与 mysql 各种 timeout 参数相关。

BatchUpdateException

这个错误是发生在数据批量导入时。当时数据量大概 20 多W条,然后在批量插入时抛出该异常。以下为批量插入代码。

getSqlMapClientTemplate().execute(new SqlMapClientCallback<Object>() {
			@Override
			public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
				executor.startBatch();
				for (int i = 0; i < 200000; i++) {
					Demo demo = new Demo();
					demo.setName("asd");
					demo.setAge(String.valueOf(i));
					demo.setSubject("adassad");
					// 原项目 这里会发生一次 RPC调用 现用 Sleep 代替
					try {
						TimeUnit.MILLISECONDS.sleep(10L);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					executor.insert("insertDemo", demo);
				}
				executor.executeBatch();
				return null;
			}
		});
复制代码

这段代码就是使用 ibatis batch 功能,批量插入数据。

其实看到这个异常信息,java.sql.BatchUpdateException: No operations allowed after statement closed 可以明确看出是因为 statement 关闭导致,那么为什么 statement 会提前关闭。下面我们跟踪源码。

现在我们先看 SqlMapClientCallback doInSqlMapClient 方法。debug executor.startBatch() 方法最后其调用的是 SqlMapExecutorDelegate.startBatch 方法

查看代码注释可知,其目的就是为了设置一个状态值,这个状态值下面将用到。

此时我们查看 executor.insert ,正常来说该方法应该会执行sql 语句,然后插入数据库。但是查看源码你会发现,他最后调用的是 MappedStatement.sqlExecuteUpdate,进入方法刚开始就判断上文设置的 session batch 属性。当然这个属性,我们刚开始已经设置成 true , 所以此时并没有执行 sql 插入动作,而是将这次 sql 以及相关参数存储到内存。

  protected int sqlExecuteUpdate(StatementScope statementScope, Connection conn, String sqlString, Object[] parameters) throws SQLException {
    if (statementScope.getSession().isInBatch()) {
      getSqlExecutor().addBatch(statementScope, conn, sqlString, parameters);
      return 0;
    } else {
      return getSqlExecutor().executeUpdate(statementScope, conn, sqlString, parameters);
    }
  }

复制代码

最后我们查看 executor.executeBatch,该方法最后调用了 Statement.executeBatch,真正开始执行批量插入。

看完 SqlMapClientCallback 里面的逻辑,现在我们来查看 SqlMapClientTemplate.execute 代码逻辑。

查看时序图可知,在真正执行 SqlMapClientCallback 回调方法逻辑时,这个时候会首先从 DataSource 获取 Connection, 然后后面开始执行 SqlMapClientCallback 回调逻辑,最后释放 Connection。这个过程中若 SqlMapClientCallback 方法执行时间过久,如我们的方法中调用 for 循环中每次都会发生一次 Dubbo 调用,然后由于这个循环需要遍历 20 多 W 数据,这就导致该循环结束就需要半个多小时(假设一次 dubbo 调用耗时 10 ms),而我们 mysql server wait_timeout 为 300s,所以 mysql server 提前主动释放空闲连接,然后等到真正执行批量插入时,就会导致上面的异常。

题外话:mysql jdbc 使用 Batch 插入时,需要设置 rewriteBatchedStatements=true 参数。若没有设置,其最后等效使用一次 for 循环插入数据,并不能提升插入的效率。

参考

  1. JDBC 驱动程序类型
  2. Understanding JDBC Internals & Timeout Configuration
  3. 深入理解 JDBC 的超时
  4. 深入分析JDBC超时机制
  5. mysql: show processlist 详解
  6. 聊聊jdbc socketTimeout的设置

如果觉得好的话,请帮作者点个赞呗~ 谢谢

喜欢本文的读者们,欢迎长按关注订阅号程序通事~让我与你分享程序那些事。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 如果您的SQL Server数据库发生卡顿,可以尝试以下方法来解决问题: 1. 检查系统资源是否紧张。如果内存或CPU使用率很高,则需要进行资源加倍或优化查询来释放资源。 2. 检查是否存在长时间运行的查询。使用SQL Server管理工具(例如SQL Server Management Studio)可以查看正在运行的查询,并分析是否需要优化。 3. 检查系统日志,了解是否存在任何故障或错误信息。 4. 在生产环境中,应安装最新的服务包和修补程序,以避免可能导致卡顿的已知问题。 5. 如果仍然无法解决问题,则可能需要与数据库管理员或技术支持人员联系,获取进一步的帮助。 ### 回答2: SQL Server数据库卡顿的问题在实际应用中经常会遇到,以下是处理这种问题的一些建议。 首先,可以通过查看系统资源利用情况来确定数据库卡顿的具体原因。可以使用Windows任务管理器或SQL Server自带的性能监视工具来查看CPU、内存和磁盘IO等指标的使用情况,以确定是否出现资源瓶颈。 其次,可以通过优化查询语句和索引来提升数据库性能。可以使用SQL Server提供的执行计划分析工具来查看查询语句的执行计划,并根据建议进行优化操作,如增加索引、重新编写查询语句等。 另外,可以设置适当的数据库参数来调整性能。例如,可以调整最大内存使用量、最大并发连接数、日志文件大小等参数,以提高数据库的性能。 此外,可以对数据库进行定期的维护和优化操作。可以通过执行数据库备份、重建索引、压缩数据库等操作来清理和优化数据库,从而提高其性能。 还可以考虑将数据库文件和日志文件分散到不同的物理磁盘上,以减轻IO负载。 最后,可以考虑升级硬件和调整服务器配置以提高数据库性能。例如,可以增加更高性能的CPU和内存、使用更快的磁盘阵列等,以满足数据库的需求。 总之,处理SQL Server数据库卡顿的问题需要综合考虑系统资源、查询语句、索引优化、数据库参数设置、定期维护等多个方面。通过适当的优化和调整,可以提高数据库的性能,解决卡顿问题。 ### 回答3: 处理SQL Server数据库卡的问题可以从以下几个方面着手: 1. 优化查询语句:检查慢查询语句,使用合适的索引、避免全表扫描等方式来提高查询效率。 2. 优化数据库结构:检查表的设计,合理拆分大表、选择合适的数据类型、规范化数据库等,以提高数据库的性能和可维护性。 3. 调整数据库配置:根据服务器的配置和负载情况,调整SQL Server的参数设置,例如内存、并发连接数、最大请求等,以提升数据库的性能。 4. 监控数据库性能:使用SQL Server Profiler工具或系统监控指标来监测数据库的性能,及时发现异常和瓶颈,并采取相应的措施进行优化。 5. 清理不必要的数据和日志:删除过期或无用的数据,定期备份和清理事务日志,以释放存储空间和提高数据库的性能。 6. 定期维护数据库执行数据库维护任务,如索引重建、统计信息更新、数据库收缩等,以减少数据库碎片和提升查询性能。 7. 调整服务器硬件:如果数据库性能问题无法通过优化软件来解决,可以考虑升级服务器硬件,增加内存、CPU或存储空间等来提升数据库性能。 8. 使用缓存和缓存技术:通过引入缓存层、使用缓存技术如Redis等,可以减少对数据库的访问,提高系统的响应速度。 综上所述,处理SQL Server数据库卡的问题需要从多个方面综合考虑,包括优化查询、数据库结构、配置、清理数据、监控性能、维护数据库、硬件升级和使用缓存等。通过系统分析和优化,可以提高数据库的性能和可靠性,有效解决卡顿问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值