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]

Reply via email to