This is an automated email from the ASF dual-hosted git repository.
funky-eyes pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 396ed94dd8 bugfix: fix TCC fence cleanup deleting
in-progress/unexpired sibling branch records (#8138)
396ed94dd8 is described below
commit 396ed94dd851042df68951621dc82497924472b5
Author: Jinyeong Seol <[email protected]>
AuthorDate: Wed Jun 24 17:59:34 2026 +0900
bugfix: fix TCC fence cleanup deleting in-progress/unexpired sibling branch
records (#8138)
---
changes/en-us/2.x.md | 3 ++
changes/zh-cn/2.x.md | 3 ++
.../tx/api/fence/store/CommonFenceStore.java | 9 ++--
.../store/db/CommonFenceStoreDataBaseDAO.java | 4 +-
.../fence/store/db/sql/CommonFenceStoreSqls.java | 20 ++++----
.../store/db/CommonFenceStoreDataBaseDAOTest.java | 9 +++-
.../store/db/sql/CommonFenceStoreSqlsTest.java | 53 ++++++++++++++++++++++
.../apache/seata/rm/fence/SpringFenceHandler.java | 2 +-
8 files changed, 86 insertions(+), 17 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index b3e7ee1683..ea951fedff 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -24,9 +24,11 @@ Add changes here for all PR submitted to the 2.x branch.
### bugfix:
+- [[#8138](https://github.com/apache/incubator-seata/pull/8138)] fix TCC fence
cleanup deleting in-progress/unexpired sibling branch records
- [#8145](https://github.com/apache/incubator-seata/pull/8145) fix global lock
batch acquire false-failure on Dameng(DM)
+
### optimize:
@@ -47,6 +49,7 @@ Thanks to these contributors for their code commits. Please
report an unintended
<!-- Please make sure your Github ID is in the list below -->
- [slievrly](https://github.com/slievrly)
+- [Seol-JY](https://github.com/Seol-JY)
- [lhozy](https://github.com/lhozy)
- [Zhengcy05](https://github.com/Zhengcy05)
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 07018ef3fe..c775406572 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -24,6 +24,7 @@
### bugfix:
+- [[#8138](https://github.com/apache/incubator-seata/pull/8138)] 修复 TCC fence
清理时误删同一全局事务中仍在进行(TRIED)或未过期的分支记录的问题
- [#8145](https://github.com/apache/incubator-seata/pull/8145)
修复达梦(DM)数据库下全局锁批量获取被误判为失败的问题
@@ -47,8 +48,10 @@
<!-- 请确保您的 GitHub ID 在以下列表中 -->
- [slievrly](https://github.com/slievrly)
+- [Seol-JY](https://github.com/Seol-JY)
- [lhozy](https://github.com/lhozy)
- [Zhengcy05](https://github.com/Zhengcy05)
+
同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。
diff --git
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/CommonFenceStore.java
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/CommonFenceStore.java
index 28a49f9c00..f4c5de822d 100644
---
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/CommonFenceStore.java
+++
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/CommonFenceStore.java
@@ -74,12 +74,15 @@ public interface CommonFenceStore {
boolean deleteCommonFenceDO(Connection conn, String xid, Long branchId);
/**
- * Delete tcc fence do boolean.
+ * Delete tcc fence by the given xids, restricted to expired end-status
rows.
+ * The datetime and end-status predicates guard against removing sibling
branch rows of the same xid
+ * that are still in progress (TRIED) or not yet expired.
* @param conn the connection
* @param xids the global transaction ids
- * @return the boolean
+ * @param datetime the expiry threshold; only rows with gmt_modified
before this are deleted
+ * @return the deleted row count
*/
- int deleteTCCFenceDO(Connection conn, List<String> xids);
+ int deleteTCCFenceDO(Connection conn, List<String> xids, Date datetime);
/**
* Set LogTable Name
diff --git
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAO.java
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAO.java
index 8a9b9fc07e..acceeba08f 100644
---
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAO.java
+++
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAO.java
@@ -180,7 +180,7 @@ public class CommonFenceStoreDataBaseDAO implements
CommonFenceStore {
}
@Override
- public int deleteTCCFenceDO(Connection conn, List<String> xids) {
+ public int deleteTCCFenceDO(Connection conn, List<String> xids, Date
datetime) {
PreparedStatement ps = null;
try {
String paramsPlaceHolder =
org.apache.commons.lang3.StringUtils.repeat("?", ",", xids.size());
@@ -189,6 +189,8 @@ public class CommonFenceStoreDataBaseDAO implements
CommonFenceStore {
for (int i = 0; i < xids.size(); i++) {
ps.setString(i + 1, xids.get(i));
}
+ // gmt_modified threshold, bound after the xid placeholders
+ ps.setTimestamp(xids.size() + 1, new
Timestamp(datetime.getTime()));
return ps.executeUpdate();
} catch (SQLException e) {
throw new StoreException(e);
diff --git
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/sql/CommonFenceStoreSqls.java
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/sql/CommonFenceStoreSqls.java
index a1d55bcf59..2ddf4ad342 100644
---
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/sql/CommonFenceStoreSqls.java
+++
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/store/db/sql/CommonFenceStoreSqls.java
@@ -56,8 +56,10 @@ public class CommonFenceStoreSqls {
/**
* The constant QUERY_END_STATUS_BY_DATE.
+ * Selects distinct xids so the query limit bounds the number of distinct
xids returned,
+ * which keeps the limit comparison in the cleanup loop consistent (one
xid may have multiple branch rows).
*/
- protected static final String QUERY_END_STATUS_BY_DATE = "select xid,
branch_id, status, gmt_create, gmt_modified "
+ protected static final String QUERY_END_STATUS_BY_DATE = "select distinct
xid "
+ " from " + LOCAL_TCC_LOG_PLACEHOLD
+ " where gmt_modified < ? "
+ " and status in (" + CommonFenceConstant.STATUS_COMMITTED + " ,
" + CommonFenceConstant.STATUS_ROLLBACKED
@@ -86,16 +88,14 @@ public class CommonFenceStoreSqls {
"delete from " + LOCAL_TCC_LOG_PLACEHOLD + " where xid = ? and
branch_id = ? ";
/**
- * The constant DELETE_BY_BRANCH_ID_AND_XID.
- */
- protected static final String DELETE_BY_BRANCH_XIDS =
- "delete from " + LOCAL_TCC_LOG_PLACEHOLD + " where xid in (" +
PRAMETER_PLACEHOLD + ")";
-
- /**
- * The constant DELETE_BY_DATE_AND_STATUS.
+ * The constant DELETE_BY_BRANCH_XIDS.
+ * The gmt_modified and status predicates must match {@link
#QUERY_END_STATUS_BY_DATE}: deleting by xid alone
+ * would also remove sibling branch rows of the same global transaction
that are still in a non-end status
+ * (e.g. TRIED) or not yet expired, which must be preserved.
*/
- protected static final String DELETE_BY_DATE_AND_STATUS = "delete from " +
LOCAL_TCC_LOG_PLACEHOLD
- + " where gmt_modified < ? "
+ protected static final String DELETE_BY_BRANCH_XIDS = "delete from " +
LOCAL_TCC_LOG_PLACEHOLD + " where xid in ("
+ + PRAMETER_PLACEHOLD + ")"
+ + " and gmt_modified < ? "
+ " and status in (" + CommonFenceConstant.STATUS_COMMITTED + " ,
" + CommonFenceConstant.STATUS_ROLLBACKED
+ " , " + CommonFenceConstant.STATUS_SUSPENDED + ")";
diff --git
a/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAOTest.java
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAOTest.java
index b1451e7964..5761fce62c 100644
---
a/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAOTest.java
+++
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAOTest.java
@@ -169,9 +169,12 @@ public class CommonFenceStoreDataBaseDAOTest {
when(connection.prepareStatement(anyString())).thenReturn(statement);
when(statement.executeUpdate()).thenReturn(2);
- assertEquals(2, dao.deleteTCCFenceDO(connection, Arrays.asList("xid1",
"xid2")));
+ assertEquals(2, dao.deleteTCCFenceDO(connection, Arrays.asList("xid1",
"xid2"), new Date(1000L)));
verify(statement).setString(1, "xid1");
verify(statement).setString(2, "xid2");
+ // the gmt_modified threshold must be bound right after the xid
placeholders,
+ // so the delete only removes expired end-status rows and never a
sibling branch row by xid alone
+ verify(statement).setTimestamp(3, new Timestamp(1000L));
}
@Test
@@ -184,7 +187,9 @@ public class CommonFenceStoreDataBaseDAOTest {
assertThrows(StoreException.class, () ->
dao.insertCommonFenceDO(connection, newFenceDO()));
assertThrows(StoreException.class, () ->
dao.updateCommonFenceDO(connection, "xid", 1L, 2, 1));
assertThrows(StoreException.class, () ->
dao.deleteCommonFenceDO(connection, "xid", 1L));
- assertThrows(StoreException.class, () ->
dao.deleteTCCFenceDO(connection, Arrays.asList("xid1", "xid2")));
+ assertThrows(
+ StoreException.class,
+ () -> dao.deleteTCCFenceDO(connection, Arrays.asList("xid1",
"xid2"), new Date(1000L)));
}
private static CommonFenceDO newFenceDO() {
diff --git
a/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/sql/CommonFenceStoreSqlsTest.java
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/sql/CommonFenceStoreSqlsTest.java
new file mode 100644
index 0000000000..59b4605f7a
--- /dev/null
+++
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/sql/CommonFenceStoreSqlsTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.integration.tx.api.fence.store.db.sql;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CommonFenceStoreSqlsTest {
+
+ private static final String TABLE = "tcc_fence_log";
+
+ /**
+ * The date-based cleanup selects distinct xids, so a row limit bounds the
number of distinct xids
+ * (one xid may own multiple branch rows) and the limit comparison in the
cleanup loop stays consistent.
+ */
+ @Test
+ public void queryEndStatusByDateSelectsDistinctXids() {
+ assertEquals(
+ "select distinct xid from tcc_fence_log where gmt_modified <
? and status in (2 , 3 , 4) limit ? ",
+ CommonFenceStoreSqls.getQueryEndStatusSQLByDate(TABLE, false));
+
+ assertEquals(
+ "select distinct xid from tcc_fence_log where gmt_modified <
? and status in (2 , 3 , 4) and ROWNUM <= ? ",
+ CommonFenceStoreSqls.getQueryEndStatusSQLByDate(TABLE, true));
+ }
+
+ /**
+ * Core regression: deleting expired fence logs by xid is also restricted
by gmt_modified and end status.
+ * Without these predicates, deleting by xid alone would purge sibling
branch rows of the same global
+ * transaction that are still in progress (TRIED) or not yet expired.
+ */
+ @Test
+ public void deleteByXidsIsRestrictedByDateAndEndStatus() {
+ assertEquals(
+ "delete from tcc_fence_log where xid in (?, ?) and
gmt_modified < ? and status in (2 , 3 , 4)",
+ CommonFenceStoreSqls.getDeleteSQLByXids(TABLE, "?, ?"));
+ }
+}
diff --git
a/spring/seata-spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java
b/spring/seata-spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java
index 994ddc4ad6..853b77c929 100644
---
a/spring/seata-spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java
+++
b/spring/seata-spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java
@@ -397,7 +397,7 @@ public class SpringFenceHandler implements FenceHandler {
if (xidSet.isEmpty()) {
break;
}
- total += COMMON_FENCE_DAO.deleteTCCFenceDO(connection, new
ArrayList<>(xidSet));
+ total += COMMON_FENCE_DAO.deleteTCCFenceDO(connection, new
ArrayList<>(xidSet), datetime);
if (xidSet.size() < LIMIT_DELETE) {
break;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]