手动开启事务,但是忘记关闭,导致其他复用了事务问题记录

4 阅读1分钟

问题

线上一直在报错,报了transaction not in one node.

找不到原因,线上的报错日志,都是一些没有开启事务的方法

dba联合排查

发现是手动开启事务后,直接return了,没有执行commit或者rollback,导致事务被其他一些sql使用到了,又由于是分片库,跨分片导致报错

细节原因


private class SqlSessionInterceptor implements InvocationHandler {

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,

                                              SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

        try {

            Object result = method.invoke(sqlSession, args);

            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {

                // force commit even on non-dirty sessions because some databases require

                // a commit/rollback before calling close()

                sqlSession.commit(true);

            }

            return result;

        } catch (Throwable t) {

            Throwable unwrapped = unwrapThrowable(t);

            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {

                // release the connection to avoid a deadlock if the translator is no loaded. See issue #22

                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

                sqlSession = null;

                Throwable translated = SqlSessionTemplate.this.exceptionTranslator

                .translateExceptionIfPossible((PersistenceException) unwrapped);

                if (translated != null) {

                    unwrapped = translated;

                }

            }

            throw unwrapped;

        } finally {

            if (sqlSession != null) {

                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

            }

        }

    }

}

  


mybatis在执行方法时,会获取sqlSession,事务相关是和sqlsession绑定的


  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,

      PersistenceExceptionTranslator exceptionTranslator) {

  


    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

  


    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  


    SqlSession session = sessionHolder(executorType, holder);

    if (session != null) {

      return session;

    }

  


    LOGGER.debug(() -> "Creating a new SqlSession");

    session = sessionFactory.openSession(executorType);

  


    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  


    return session;

  }


@Nullable

public static Object getResource(Object key) {

    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);

    Object value = doGetResource(actualKey);

    if (value != null && logger.isTraceEnabled()) {

       logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +

             Thread.currentThread().getName() + "]");

    }

    return value;

}


private static final ThreadLocal<Map<Object, Object>> resources =

new NamedThreadLocal<>("Transactional resources");

  


@Nullable

private static Object doGetResource(Object actualKey) {

    Map<Object, Object> map = resources.get();

    if (map == null) {

       return null;

    }

    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;

}

事务是通过threadLocal来传递的,由于我没有关闭事务,这个线程又是被tomcat共用,导致另外一个请求获取到了这个事务。最终触发分片库的not in one node