最近线上环境的某个服务,总是会时不时提示连接数不够类似的提示,甚至还会有死锁的情况发生,后来经过排查,发现是某段核心代码使用编程式事务的时候,某个分支下忘记对事务进行提交,导致事务不断的进行积累,导致连接数被长时间占用,甚至出现了死锁问题。
我自己在本地想复现的时候,发现了一个有趣的现象,所以粗略的看了一下获取事务的流程源码。首先本地的测试代码就模拟一个简单的未提交事务的情况,同时设置mysql的最大连接数为20。
public Member findMember(String username) {
TransactionStatus transaction = transactionTemplate.getTransactionManager().getTransaction(null);
Member member = memberService.findMember(username, null);
return member;
}
循环50次调用接口
本来按我的理解,认为应该是会发生连接数不够的情况,但实际上全部运行成功了,而且数据库里的事务数只有10个,这下,把我搞蒙了,为啥事务数低于10会产生新的事务,高于10之后就不会产生了新的事务。
点开getTransaction的源码,真相必定就在其中了。
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
//获取事务类型,null则是默认的事务类型
TransactionDefinition def = definition != null ? definition : TransactionDefinition.withDefaults();
//获取事务
Object transaction = this.doGetTransaction();
boolean debugEnabled = this.logger.isDebugEnabled();
//判断事务是否存在
if (this.isExistingTransaction(transaction)) {
// 返回已存在的事务
return this.handleExistingTransaction(def, transaction, debugEnabled);
} else if (def.getTimeout() < -1) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
} else if (def.getPropagationBehavior() == 2) {
throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
//事务类型判断
} else if (def.getPropagationBehavior() != 0 && def.getPropagationBehavior() != 3 && def.getPropagationBehavior() != 6) {
if (def.getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = this.getTransactionSynchronization() == 0;
return this.prepareTransactionStatus(def, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
} else { //事务不存在,则开启新事物
SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
if (debugEnabled) {
this.logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
return this.startTransaction(def, transaction, debugEnabled, suspendedResources);
} catch (Error | RuntimeException var7) {
this.resume((Object)null, suspendedResources);
throw var7;
}
}
}
结构十分的清晰,点开isExistingTransaction(),查看判断事务是否存在的条件。
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
//当前对象是否有连接对象,连接对象中的事务是否是活跃状态
return txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive();
}
回到上一步,再点开doGetTransaction()
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
//是否运行事务嵌套
txObject.setSavepointAllowed(this.isNestedTransactionAllowed());
//获取连接对象
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.obtainDataSource());
//设置连接对象
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
点开getResource(),又调用了doGetResource
public static Object getResource(Object key) {
//构建key
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
return doGetResource(actualKey);
}
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
@Nullable //nullable这个习惯,真心建议大家都使用一下,避免空指针的发生
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
//获取ConnectionHolder
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
到这里就差不多知道原因了,事务是否存在,是根据ConnectionHolder是否存在判断的,而ConnectionHolder对象是保存的ThreadLocal中,而ThreadLocal大家也都知道,是每个线程都会维护一份,所以你有几个线程就只有几个ThreadLocal,而springboot默认使用的tomcat的minSpareThreads空闲线程数也正好就是10,最终导致了这个情况的发生。重新设置一下minSpareThreads线程数,果然50个事务了。