This is an automated email from the ASF dual-hosted git repository.

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 138da30a7e7 [fix](fe) Return unknown stats for system tables (#62913)
138da30a7e7 is described below

commit 138da30a7e7272f1b2adca80345b8048a292319a
Author: yujun <[email protected]>
AuthorDate: Wed May 6 14:56:29 2026 +0800

    [fix](fe) Return unknown stats for system tables (#62913)
    
    ### What problem does this PR solve?
    
    Related PR: introduced by #41790
    
    Problem Summary:
    
    We met a case where manually dropping column stats tablet files caused
    many internal queries against `__internal_schema.column_statistics`,
    including queries whose target stats IDs belong to the statistics table
    itself. The visible symptom is a self-amplifying internal-query storm
    such as:
    
    ```sql
    SELECT * FROM __internal_schema.column_statistics
    WHERE id IN (... column_statistics own column stats ids ...)
    ```
    
    The problematic call chain is:
    
    ```text
    ColumnStatisticsCacheLoader.doLoad
      -> StatisticsRepository.loadColStats
      -> StatisticsUtil.execStatisticQuery
      -> StmtExecutor.executeInternalQuery
      -> NereidsPlanner.optimize
      -> InitJoinOrder
      -> StatsCalculator.disableJoinReorderIfStatsInvalid
      -> StatsCalculator.checkNdvValidation
      -> StatisticsCache.OlapTableStatistics.getColumnStatistics
    ```
    
    When the internal stats-loading SQL scans
    `__internal_schema.column_statistics`, join-reorder's pre-validation
    path runs before `computeOlapScan()` derives UNKNOWN stats for system
    tables. That pre-validation can request column statistics for
    `column_statistics` itself. If the stats load fails, the async stats
    cache does not retain a useful value for the failed key, so repeated
    planning can reload the same system-table stats and amplify the internal
    query volume.
    
    This is separate from the audit `State=OK` display issue: those audit
    rows can look successful even when the internal stats query failed. The
    bug fixed here is the recursive system-table stats lookup during
    planning.
    
    This PR returns UNKNOWN from `StatisticsCache.OlapTableStatistics`
    column and partition-column accessors for system tables. That keeps the
    system-table behavior consistent with `computeOlapScan()` and prevents
    future callers through this accessor from accidentally loading
    system-table stats before the normal scan-statistics guard.
    
    The fix intentionally does not skip all internal queries. Internal jobs
    that scan normal user tables, such as import, MV refresh, or internal
    insert tasks, can still use normal stats validation and optimization.
---
 .../apache/doris/statistics/StatisticsCache.java   |  6 +++
 .../doris/statistics/StatisticsCacheTest.java      | 46 ++++++++++++++++++++++
 2 files changed, 52 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsCache.java 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsCache.java
index e8a5c519660..6ba538b81a3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsCache.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsCache.java
@@ -401,6 +401,9 @@ public class StatisticsCache {
             if (ctx != null && ctx.getState().isPlanWithUnKnownColumnStats()) {
                 return ColumnStatistic.UNKNOWN;
             }
+            if (StatisticConstants.isSystemTable(olapTable)) {
+                return ColumnStatistic.UNKNOWN;
+            }
             return doGetColumnStatistics(
                     catalogId, schemaId, tableId, selectIndexId, colName, ctx
             );
@@ -411,6 +414,9 @@ public class StatisticsCache {
             if (ctx != null && ctx.getState().isPlanWithUnKnownColumnStats()) {
                 return PartitionColumnStatistic.UNKNOWN;
             }
+            if (StatisticConstants.isSystemTable(olapTable)) {
+                return PartitionColumnStatistic.UNKNOWN;
+            }
             return doGetPartitionColumnStatistics(
                     catalogId, schemaId, tableId, selectIndexId, partName, 
colName
             );
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/StatisticsCacheTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/StatisticsCacheTest.java
index cb1dffbe106..712be0ef0b6 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/StatisticsCacheTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/StatisticsCacheTest.java
@@ -17,6 +17,11 @@
 
 package org.apache.doris.statistics;
 
+import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.common.FeConstants;
+import org.apache.doris.datasource.CatalogIf;
+import org.apache.doris.nereids.trees.plans.algebra.OlapScan;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.utframe.UtFrameUtils;
 
@@ -24,6 +29,8 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
 
 public class StatisticsCacheTest {
 
@@ -87,4 +94,43 @@ public class StatisticsCacheTest {
             
ConnectContext.get().getState().setPlanWithUnKnownColumnStats(prevFlag);
         }
     }
+
+    @Test
+    public void testOlapTableStatisticsSkipSystemTable() {
+        try (MockedConstruction<ColumnStatisticsCacheLoader> columnLoader = 
Mockito.mockConstruction(
+                ColumnStatisticsCacheLoader.class);
+                MockedConstruction<PartitionColumnStatisticCacheLoader> 
partitionLoader = Mockito.mockConstruction(
+                        PartitionColumnStatisticCacheLoader.class)) {
+            StatisticsCache cache = new StatisticsCache();
+            StatisticsCache.OlapTableStatistics olapTableStats =
+                    
cache.getOlapTableStats(mockSystemOlapScan(FeConstants.INTERNAL_DB_NAME));
+
+            Assertions.assertEquals(ColumnStatistic.UNKNOWN,
+                    olapTableStats.getColumnStatistics("col", 
ConnectContext.get()));
+            Assertions.assertEquals(PartitionColumnStatistic.UNKNOWN,
+                    olapTableStats.getPartitionColumnStatistics("p1", "col", 
ConnectContext.get()));
+
+            Mockito.verifyNoInteractions(columnLoader.constructed().get(0));
+            Mockito.verifyNoInteractions(partitionLoader.constructed().get(0));
+        }
+    }
+
+    private OlapScan mockSystemOlapScan(String dbName) {
+        CatalogIf catalog = Mockito.mock(CatalogIf.class);
+        Mockito.when(catalog.getId()).thenReturn(1L);
+        DatabaseIf database = Mockito.mock(DatabaseIf.class);
+        Mockito.when(database.getId()).thenReturn(2L);
+        Mockito.when(database.getCatalog()).thenReturn(catalog);
+
+        OlapTable table = Mockito.mock(OlapTable.class);
+        Mockito.when(table.getQualifiedDbName()).thenReturn(dbName);
+        Mockito.when(table.getDatabase()).thenReturn(database);
+        Mockito.when(table.getId()).thenReturn(3L);
+        Mockito.when(table.getBaseIndexId()).thenReturn(4L);
+
+        OlapScan scan = Mockito.mock(OlapScan.class);
+        Mockito.when(scan.getTable()).thenReturn(table);
+        Mockito.when(scan.getSelectedIndexId()).thenReturn(4L);
+        return scan;
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to