java.sql.Connection 线程安全吗?

首先看一段代码:

try (Connection connection = DriverManager.getConnection("jdbc:mysql://remote01:3306/test");) {
	ExecutorService executor = ThreadUtil.newExecutor();
	for (int i = 0; i < 1000; i++) {
		int finalI = i;
		// 使用线程池,并发执行下面语句
		executor.submit(() -> {
			try {
			  // 通过连接创建 Statement
				PreparedStatement statement = connection.prepareStatement("select * from user limit ?,100");
				statement.setInt(1, finalI * 100);
				// 执行查询操作
				statement.execute();
				// 获取返回结果
				ResultSet resultSet = statement.getResultSet();
				while (resultSet.next()) {
					System.out.println(resultSet.getString("name"));
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		});
	}
} catch (SQLException e) {
	e.printStackTrace();
}

上面这段代码中,出现了线程池中多个线程共用一个 SQL 连接 的现象。一般我们会使用一些连接池来管理 SQL 连接,而这些连接池通常会为每个线程单独分配一个连接,来确保线程安全的问题。也就是说,如果出现了多个线程共用一个 Sql 连接,正常理解来说会出现线程安全问题,那么真的是这样的吗?

ExecutorService executor = ThreadUtil.newExecutor();
for (int i = 0; i < 1000; i++) {
    // 使用线程池,并发执行下面语句
    final int finalI = i;
    executor.submit(() -> {
        List<Map<String, Object>> resultSet = jdbcTemplate.queryForList("select * from user limit ?,100", finalI * 100);
        // 获取返回结果
        resultSet.forEach(res -> System.out.println(res.get("name")));
    });
}

上面这段代码使用了 jdbcTemplate 来执行 SQL 语句,这种情况下SQL 的连接管理就统一交由连接池去做了,jdbcTempalte 会在执行SQL前从连接池中拿到一个SQL 连接去执行SQL,连接池会根据当前线程名称为我们分配一个固定的SQL连接,确保一个线程对应一个SQL连接,而 jdbcTemplate 会在SQL执行完成后会将SQL连接放回至连接池中以备稍后使用,而不是直接关掉。从而提高SQL连接的利用率。

结论#

非事务场景#

Connection 没有开启事务的情况下,如单纯的 Select 查询语句,或开启了 AUTO_COMMIT,是线程安全的。但是由于内部使用了 (synchronized),同步锁,在多线程访问一个 Connection 时,会导致执行请求的串行化。

事务场景#

如果在开启事务的情况下,会导致多个线程共用一个事务,也就是说如果其中一个线程对事务进行了提交或者回滚操作,会同时影响其他线程执行的SQL。

上代码#

从结论可以得到,SQL连接虽然总体上非线程安全,但是在非事务场景中还是保证了线程安全的,那么 Connection 是怎么保证这一点的呢?

通常我们使用 Connection 来执行查询,通过Connection获取一个 Statement 对象 ,然后调用Statement中的 execute 方法来执行真正的 sql。我们可以通过解析 Statement实现中的 execute 方法来得到 SQL 执行的具体逻辑。

// com.mysql.jdbc.StatementImpl#execute(java.lang.String, boolean)
private boolean execute(String sql, boolean returnGeneratedKeys) throws SQLException {
	MySQLConnection locallyScopedConn = checkClosed();
	// 使用同步代码块对单次查询加锁
	synchronized (locallyScopedConn.getConnectionMutex()) {
		// 重置并且检查当前连接的状态
		this.retrieveGeneratedKeys = returnGeneratedKeys;
		lastQueryIsOnDupKeyUpdate = false;
		if (returnGeneratedKeys)
			lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyInString(sql);
		resetCancelledState();
		checkNullOrEmptyQuery(sql);
		checkClosed();
		char firstNonWsChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql));
		boolean isSelect = true;
		if (firstNonWsChar != 'S') {
			// 检查状态
		}
		boolean doStreaming = createStreamingResultSet();
		try {
			// 执行查询,并且处理返回结果
			...
		} finally {
			this.statementExecuting.set(false);
		}
	}
}

上面是 mysql 驱动中 Statement 执行具体 SQL 的逻辑实现,可以看到在执行开始,就通过当前连接设置了一个同步代码块。这样的话,当前 Connection 所创建的所有 Statement 同时执行 execute 方法时,实际是串行执行的。

// 提交多线程并发执行任务
executor.submit(() -> {
	try {
	  // 每个线程执行时会单独创建一个 Statement
		PreparedStatement statement = connection.prepareStatement("select * from user limit ?,100");
		statement.setInt(1, finalI * 100);
		// 执行该方法时,由于 execute 方法中的同步代码块
		// 实际查询在并发场景中是串行执行的
		statement.execute();
		ResultSet resultSet = statement.getResultSet();
		while (resultSet.next()) {
			System.out.println(resultSet.getString("name"));
		}
	} catch (SQLException e) {
		e.printStackTrace();
	}
});

现在我们了解了,单个连接创建的多个SQL查询,实际是通过代码块的方式实现了线程安全,也就是在单个连接在多线程并发执行查询的场景下,不同的查询之间是互不干扰的。但由于同步代码块的存在,多个并发查询实际是串行执行的,所以使用单个连接并发查询会导致最终查询的效果还是串行的,并没有因为使用并发而提高效率。

事务场景#

当然上面的分析是基于非事务场景下,查询之间是没有联系的。或者我们开启了自动提交(AUTO_COMMIT),这样每个SQL之间的操作也是互不关联的。但是在事务场景中,如果多个线程共用一个 SQL 连接去执行 SQL 的话,会导致多个线程共用同一个事务,如果某个线程回滚了一个事务,则会导致会将其他线程在事务期间执行的SQL一起给回滚了,而其他线程却对此一无所知,最终导致数据不一致。

image

如上图,线程A 开启了一个事务并执行相应的SQL操作。此时线程B共用同一个连接执行SQL的话,线程B是不知道它此时正处于线程A的事务当中,这样当线程A在请求回滚事务时,MySQL 会将线程B 在事务过程中提交的SQL一起给回滚掉。但所有的操作线程B 是无感知的。

参考资料#

JDBC中Connection解惑

Connection实例是线程安全的吗

JDBC中的connection详解

作者:ghimi

出处:https://www.cnblogs.com/ghimi/p/16994159.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   ghimi  阅读(474)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示