Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

同一个PreparedStatement执行多次execute会报ShouldNeverHappenException #7100

Open
1 task
xiaoxiangyeyu0 opened this issue Jan 9, 2025 · 14 comments
Open
1 task
Assignees
Labels
good first issue Good for newcomers task: help-wanted Extra attention is needed type: bug Category issues or prs related to bug.

Comments

@xiaoxiangyeyu0
Copy link
Contributor

xiaoxiangyeyu0 commented Jan 9, 2025

  • I have searched the issues of this repository and believe that this is not a duplicate.

Ⅰ. Issue Description

报错方法在io.seata.rm.BaseDataSourceResource.hold(String, T),产生来源为mybatis-plus使用com.baomidou.mybatisplus.extension.service.IService.updateBatchById(Collection)对象存在不同更新属性时,会有多个PreparedStatement,有@GlobalTransactional同时也加入@transactional注解就不会报错。

伪代码如下,把手动提交事务打开就没问题,同上面使用mybatis-plus加入@transactional时,调用io.seata.rm.datasource.xa.ExecuteTemplateXA.execute(AbstractConnectionProxyXA, StatementCallback<T, S>, S, Object...)方法就不会执行connectionProxyXA.setAutoCommit(false);

`@Resource
private DataSource dataSource;

@GlobalTransactional(rollbackFor = Exception.class)
//@Transactional(rollbackFor = Exception.class)
public void test() throws SQLException{
	Connection connection = null;
	try {
		connection = dataSource.getConnection();
                   //connection.setAutoCommit(false);
	} catch (SQLException e) {
		throw e;
	}
	try {
		PreparedStatement prepareStatement = connection.prepareStatement("update test set version = version + 1 where id = ?");
		prepareStatement.setString(1, "1");
		prepareStatement.executeUpdate();
		prepareStatement.clearParameters();
		prepareStatement.setString(1, "2");
		prepareStatement.executeUpdate();
           //connection.commit();
	} catch (SQLException e) {
		throw e;
	}
	try {
             connection.close();
	} catch (SQLException e) {
		throw e;
	}
}`

Ⅱ. Describe what happened

If there is an exception, please attach the exception trace:

Just paste your stack trace here!

Ⅲ. Describe what you expected to happen

Ⅳ. How to reproduce it (as minimally and precisely as possible)

  1. xxx
  2. xxx
  3. xxx

Minimal yet complete reproducer code (or URL to code):

Ⅴ. Anything else we need to know?

Ⅵ. Environment:

  • JDK version(e.g. java -version):
  • Seata client/server version:
  • Database version:
  • OS(e.g. uname -a):
  • Others:
@funky-eyes
Copy link
Contributor

不提供异常信息如何排查?
How to check if you don't provide exception information?

@xiaoxiangyeyu0
Copy link
Contributor Author

mybatis-plus调用报错
io.seata.common.exception.ShouldNeverHappenException: something wrong with keeper, keeping[null] but[io.seata.rm.datasource.xa.ConnectionProxyXA@59709da6] is also kept with the same key[172.16.100.12:8091:18612986048550963-18612986048550969]
at io.seata.rm.BaseDataSourceResource.hold(BaseDataSourceResource.java:176)
at io.seata.rm.datasource.xa.ConnectionProxyXA.keepIfNecessary(ConnectionProxyXA.java:103)
at io.seata.rm.datasource.xa.ConnectionProxyXA.setAutoCommit(ConnectionProxyXA.java:200)
at io.seata.rm.datasource.xa.ExecuteTemplateXA.execute(ExecuteTemplateXA.java:41)
at io.seata.rm.datasource.xa.StatementProxyXA.executeBatch(StatementProxyXA.java:88)
at sun.reflect.GeneratedMethodAccessor274.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:78)
at com.sun.proxy.$Proxy459.executeBatch(Unknown Source)
at com.baomidou.mybatisplus.core.executor.MybatisBatchExecutor.doFlushStatements(MybatisBatchExecutor.java:135)
at org.apache.ibatis.executor.BaseExecutor.flushStatements(BaseExecutor.java:129)
at org.apache.ibatis.executor.BaseExecutor.flushStatements(BaseExecutor.java:122)
at com.baomidou.mybatisplus.core.executor.MybatisCachingExecutor.flushStatements(MybatisCachingExecutor.java:218)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63)
at com.sun.proxy.$Proxy456.flushStatements(Unknown Source)
at org.apache.ibatis.session.defaults.DefaultSqlSession.flushStatements(DefaultSqlSession.java:252)
at com.baomidou.mybatisplus.extension.toolkit.SqlHelper.lambda$executeBatch$0(SqlHelper.java:213)
at com.baomidou.mybatisplus.extension.toolkit.SqlHelper.executeBatch(SqlHelper.java:175)
at com.baomidou.mybatisplus.extension.toolkit.SqlHelper.executeBatch(SqlHelper.java:207)
at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.executeBatch(ServiceImpl.java:239)
at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.updateBatchById(ServiceImpl.java:191)
at com.baomidou.mybatisplus.extension.service.IService.updateBatchById(IService.java:177)

@funky-eyes
Copy link
Contributor

connection.setAutoCommit(false) 如果不设置为false,那么一次dml都需要注册分支,而这个connection已经被上一次动作注册过分支了。

@xiaoxiangyeyu0
Copy link
Contributor Author

这个我能理解,但针对mybatis-plus的场景,方法加了@GlobalTransactional,是否应该包含@transactional的所有功能呢,要不然使用分布式事务两个注解都需要加

@funky-eyes
Copy link
Contributor

@GlobalTransactional的作用是管理分布式事务,而@transactional是用户自行选择,并且@transactional是属于spring的,@GlobalTransactional属于seata,seata不应该去限制用户使用connection的行为。而是按照jdbc规范,当autocommit为true,那么每次execute都应该提交对应的事务,根据分布式xa来说,这个xa事务就已经完成,需要一个新的connection来完成该动作。(当然在不使用xa来说,这个动作是可以完成的)或许应该在这种场景上将xa的TMJOIN功能使用上

@funky-eyes funky-eyes added task: help-wanted Extra attention is needed type: bug Category issues or prs related to bug. labels Jan 10, 2025
@xiaoxiangyeyu0
Copy link
Contributor Author

感谢解答!xa的TMJOIN功能后续有计划使用上吗?因为在不使用xa情况下,connection自动提交事务下statement是支持被执行多次的

@funky-eyes funky-eyes added the good first issue Good for newcomers label Jan 10, 2025
@funky-eyes
Copy link
Contributor

我理解就是在connection已经存在branchid的情况下并且autocommit是true,说明这个connection是被复用的继续执行sql了,理论上应该在这种情况下不去创建新的分支(因为如果xaBranchXid不为空,说明这个连接没有close,属于还在使用阶段),然后利用原本的xaBranchXid,startxa时flag为TMJOIN
你有兴趣提个pr来解决这个问题吗?可以在connectionProxyxa#setAutoCommit 中进行判断,然后走到一个join方法(参考start方法,然后修改flag为TMJOIN),而不是start方法

@funky-eyes
Copy link
Contributor

不过我记得之前oracle那边会默认隐式join,如果一个事务已经进入了prepare阶段,貌似是join不了的,如果是这样,可能需要将事务的prepare纳入到close阶段,而不是commit阶段
But I remember that the oracle will default to implicit join before. If a transaction has entered the prepare stage, it seems that it cannot be joined. If so, you may need to incorporate the prepare stage of the transaction into the close stage instead of the commit stage

@xiaoxiangyeyu0
Copy link
Contributor Author

imagesetAutoCommit方法加入,存在branchid的情况下并且autocommit是true判断,start方法使用传入的flags,PG也需要先close,但是org.postgresql.xa.PGXAConnection没有close方法的支持,这种如何处理呢?

@funky-eyes
Copy link
Contributor

这块不用担心,只要将prepare挪到在close阶段中进行,connectionproxyxa只是包装了xa的逻辑,底层的connection逻辑如何理论上不需要太过担心,只要准守jdbc规范即可。connectionproxyxa的close只是为了清除上下文等行为。

@xiaoxiangyeyu0
Copy link
Contributor Author

prepare需要挪到在ConnectionProxyXA.xaCommit阶段执行前,因为close在xaCommit之后,prepare放在close,执行xaCommit会报错,我不知道挪到xaCommit有什么其他影响,目前我测试是没问题的

@xiaoxiangyeyu0
Copy link
Contributor Author

我上面说的prepare放在xaCommit执行前有问题,在close进行prepare是可以的,测了多种场景没有问题。一起的改动有上午setAutoCommit,和下面commit和close方法
image
image

@funky-eyes
Copy link
Contributor

提交pr吧,我整体review下

@funky-eyes
Copy link
Contributor

钉钉号发我[email protected]邮箱,我拉你进贡献者群中

xiaoxiangyeyu0 pushed a commit to xiaoxiangyeyu0/incubator-seata that referenced this issue Jan 11, 2025
xiaoxiangyeyu0 pushed a commit to xiaoxiangyeyu0/incubator-seata that referenced this issue Jan 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers task: help-wanted Extra attention is needed type: bug Category issues or prs related to bug.
Projects
None yet
Development

No branches or pull requests

2 participants