diff --git a/docs/en/docs/admin-manual/config/fe-config.md b/docs/en/docs/admin-manual/config/fe-config.md
index 6bb154f572..2d35568f49 100644
--- a/docs/en/docs/admin-manual/config/fe-config.md
+++ b/docs/en/docs/admin-manual/config/fe-config.md
@@ -2701,6 +2701,19 @@ If false, when select from tables in information_schema database,
the result will not contain the information of the table in external catalog.
This is to avoid query time when external catalog is not reachable.
+
+#### `enable_query_hit_stats`
+
+
+
+Default: false
+
+IsMutable: true
+
+MasterOnly: false
+
+Controls whether to enable query hit statistics. The default is false.
+
#### `max_instance_num`
@@ -2708,4 +2721,4 @@ This is to avoid query time when external catalog is not reachable.
Default: 128
This is used to limit the setting of "parallel_fragment_exec_instance_num".
-"parallel_fragment_exec_instance_num" cannot be set higher than "max_instance_num".
\ No newline at end of file
+"parallel_fragment_exec_instance_num" cannot be set higher than "max_instance_num".
diff --git a/docs/en/docs/admin-manual/http-actions/fe/query-stats-action.md b/docs/en/docs/admin-manual/http-actions/fe/query-stats-action.md
new file mode 100644
index 0000000000..9ef5035ea1
--- /dev/null
+++ b/docs/en/docs/admin-manual/http-actions/fe/query-stats-action.md
@@ -0,0 +1,99 @@
+---
+{
+"title": "Query Stats Action",
+"language": "en"
+}
+---
+
+
+
+# Query Stats Action
+
+
+
+## Request
+
+```
+查看
+get api/query_stats/
+get api/query_stats//
+get api/query_stats///
+
+清空
+delete api/query_stats//
+delete api/query_stats///
+```
+
+## Description
+
+Get or delete the statistics information of the specified catalog database or table, if it is a doris catalog, you can use default_cluster
+
+## Path parameters
+
+* ``
+ specified catalog name
+*
+* ``
+ specified database name
+
+* ``
+ specified table name
+
+## Query parameters
+* `summary`
+ if true, only return summary information, otherwise return all the detailed statistics information of the table, only used in get
+
+## Request body
+
+```
+GET /api/query_stats/default_cluster/test_query_db/baseall?summary=false
+{
+ "msg": "success",
+ "code": 0,
+ "data": {
+ "summary": {
+ "query": 2
+ },
+ "detail": {
+ "baseall": {
+ "summary": {
+ "query": 2
+ }
+ }
+ }
+ },
+ "count": 0
+}
+
+```
+
+## Response
+
+* return statistics information
+
+
+## Example
+
+
+2. use curl
+
+ ```
+ curl --location -u root: 'http://127.0.0.1:8030/api/query_stats/default_cluster/test_query_db/baseall?summary=false'
+ ```
diff --git a/docs/en/docs/sql-manual/sql-reference/Show-Statements/SHOW-QUERY-STATS.md b/docs/en/docs/sql-manual/sql-reference/Show-Statements/SHOW-QUERY-STATS.md
new file mode 100644
index 0000000000..1214272c97
--- /dev/null
+++ b/docs/en/docs/sql-manual/sql-reference/Show-Statements/SHOW-QUERY-STATS.md
@@ -0,0 +1,196 @@
+---
+{
+ "title": "SHOW-QUERY-STATS",
+ "language": "en"
+}
+---
+
+
+
+## SHOW-QUERY-STATS
+
+### Name
+
+
+SHOW QUERY STATS
+
+
+### Description
+
+This statement is used to show the query hit statistics of the database and table
+
+grammar:
+
+```sql
+SHOW QUERY STATS [[FOR db_name]|[FROM table_name]] [ALL] [VERBOSE]];
+```
+
+Remarks:
+
+1. Support query database and table history query hit statistics, restart fe after data will be reset, each fe separately statistics
+2. Use FOR DATABASE and FROM TABLE to specify the query database or table hit statistics, respectively followed by the database name or table name
+3. ALL can specify whether to display all index query hit statistics, VERBOSE can display more detailed hit statistics, these two parameters can be used separately, but must be placed at the end and can only be used on table queries
+4. If no database is used, execute `SHOW QUERY STATS` directly to display the hit statistics of all databases
+5. The result may have two columns:
+ QueryCount: The number of times the column was queried
+ FilterCount: The number of times the column was queried as a where condition
+### Example
+
+1. Show the query hit statistics for `baseall`
+
+ ```sql
+ MySQL [test_query_db]> show query stats from baseall;
+ +-------+------------+-------------+
+ | Field | QueryCount | FilterCount |
+ +-------+------------+-------------+
+ | k0 | 0 | 0 |
+ | k1 | 0 | 0 |
+ | k2 | 0 | 0 |
+ | k3 | 0 | 0 |
+ | k4 | 0 | 0 |
+ | k5 | 0 | 0 |
+ | k6 | 0 | 0 |
+ | k10 | 0 | 0 |
+ | k11 | 0 | 0 |
+ | k7 | 0 | 0 |
+ | k8 | 0 | 0 |
+ | k9 | 0 | 0 |
+ | k12 | 0 | 0 |
+ | k13 | 0 | 0 |
+ +-------+------------+-------------+
+ 14 rows in set (0.002 sec)
+
+ MySQL [test_query_db]> select k0, k1,k2, sum(k3) from baseall where k9 > 1 group by k0,k1,k2;
+ +------+------+--------+-------------+
+ | k0 | k1 | k2 | sum(`k3`) |
+ +------+------+--------+-------------+
+ | 0 | 6 | 32767 | 3021 |
+ | 1 | 12 | 32767 | -2147483647 |
+ | 0 | 3 | 1989 | 1002 |
+ | 0 | 7 | -32767 | 1002 |
+ | 1 | 8 | 255 | 2147483647 |
+ | 1 | 9 | 1991 | -2147483647 |
+ | 1 | 11 | 1989 | 25699 |
+ | 1 | 13 | -32767 | 2147483647 |
+ | 1 | 14 | 255 | 103 |
+ | 0 | 1 | 1989 | 1001 |
+ | 0 | 2 | 1986 | 1001 |
+ | 1 | 15 | 1992 | 3021 |
+ +------+------+--------+-------------+
+ 12 rows in set (0.050 sec)
+
+ MySQL [test_query_db]> show query stats from baseall;
+ +-------+------------+-------------+
+ | Field | QueryCount | FilterCount |
+ +-------+------------+-------------+
+ | k0 | 1 | 0 |
+ | k1 | 1 | 0 |
+ | k2 | 1 | 0 |
+ | k3 | 1 | 0 |
+ | k4 | 0 | 0 |
+ | k5 | 0 | 0 |
+ | k6 | 0 | 0 |
+ | k10 | 0 | 0 |
+ | k11 | 0 | 0 |
+ | k7 | 0 | 0 |
+ | k8 | 0 | 0 |
+ | k9 | 1 | 1 |
+ | k12 | 0 | 0 |
+ | k13 | 0 | 0 |
+ +-------+------------+-------------+
+ 14 rows in set (0.001 sec)
+ ```
+
+2. Show the query hit statistics summary for all the mv in a table
+
+ ```sql
+ MySQL [test_query_db]> show query stats from baseall all;
+ +-----------+------------+
+ | IndexName | QueryCount |
+ +-----------+------------+
+ | baseall | 1 |
+ +-----------+------------+
+ 1 row in set (0.005 sec)
+ ```
+
+3. Show the query hit statistics detail info for all the mv in a table
+
+ ```sql
+ MySQL [test_query_db]> show query stats from baseall all verbose;
+ +-----------+-------+------------+-------------+
+ | IndexName | Field | QueryCount | FilterCount |
+ +-----------+-------+------------+-------------+
+ | baseall | k0 | 1 | 0 |
+ | | k1 | 1 | 0 |
+ | | k2 | 1 | 0 |
+ | | k3 | 1 | 0 |
+ | | k4 | 0 | 0 |
+ | | k5 | 0 | 0 |
+ | | k6 | 0 | 0 |
+ | | k10 | 0 | 0 |
+ | | k11 | 0 | 0 |
+ | | k7 | 0 | 0 |
+ | | k8 | 0 | 0 |
+ | | k9 | 1 | 1 |
+ | | k12 | 0 | 0 |
+ | | k13 | 0 | 0 |
+ +-----------+-------+------------+-------------+
+ 14 rows in set (0.017 sec)
+ ```
+
+4. Show the query hit for a database
+
+ ```sql
+ MySQL [test_query_db]> show query stats for test_query_db;
+ +----------------------------+------------+
+ | TableName | QueryCount |
+ +----------------------------+------------+
+ | compaction_tbl | 0 |
+ | bigtable | 0 |
+ | empty | 0 |
+ | tempbaseall | 0 |
+ | test | 0 |
+ | test_data_type | 0 |
+ | test_string_function_field | 0 |
+ | baseall | 1 |
+ | nullable | 0 |
+ +----------------------------+------------+
+ 9 rows in set (0.005 sec)
+ ```
+
+5. Show query hit statistics for all the databases
+
+ ```sql
+ MySQL [(none)]> show query stats;
+ +-----------------+------------+
+ | Database | QueryCount |
+ +-----------------+------------+
+ | test_query_db | 1 |
+ +-----------------+------------+
+ 1 rows in set (0.005 sec)
+ ```
+ SHOW QUERY STATS;
+ ```
+
+### Keywords
+
+ SHOW, QUERY, STATS;
+
+### Best Practice
diff --git a/docs/en/docs/sql-manual/sql-reference/Utility-Statements/CLEAN-QUERY-STATS.md b/docs/en/docs/sql-manual/sql-reference/Utility-Statements/CLEAN-QUERY-STATS.md
new file mode 100644
index 0000000000..d7e0776de0
--- /dev/null
+++ b/docs/en/docs/sql-manual/sql-reference/Utility-Statements/CLEAN-QUERY-STATS.md
@@ -0,0 +1,75 @@
+---
+{
+ "title": "CLEAN-QUERY-STATS",
+ "language": "en"
+}
+---
+
+
+
+## CLEAN-QUERY-STATS
+
+### Name
+
+
+CLEAN QUERY STATS
+
+
+### Description
+
+This statement is used to clear query statistics
+
+grammar:
+
+```sql
+CLEAN [ALL| DATABASE | TABLE] QUERY STATS [[FOR db_name]|[FROM|IN] table_name]];
+```
+
+Remarks:
+
+1. If ALL is specified, all query statistics are cleared, including database and table, admin privilege is needed
+2. If DATABASE is specified, the query statistics of the specified database are cleared, alter privilege for this database is needed
+3. If TABLE is specified, the query statistics of the specified table are cleared, alter privilege for this table is needed
+
+### Example
+
+1. Clear all statistics
+2.
+ ```sql
+ clean all query stats;
+ ```
+
+2. Clear the specified database statistics
+
+ ```sql
+ clean database query stats for test_query_db;
+ ```
+3. Clear the specified table statistics
+
+ ```sql
+ clean table query stats from test_query_db.baseall;
+ ```
+
+### Keywords
+
+ CLEAN, QUERY, STATS
+
+### Best Practice
+
diff --git a/docs/sidebars.json b/docs/sidebars.json
index f27813a884..7c7e554166 100644
--- a/docs/sidebars.json
+++ b/docs/sidebars.json
@@ -984,7 +984,8 @@
"sql-manual/sql-reference/Show-Statements/SHOW-TABLE-ID",
"sql-manual/sql-reference/Show-Statements/SHOW-SMALL-FILES",
"sql-manual/sql-reference/Show-Statements/SHOW-POLICY",
- "sql-manual/sql-reference/Show-Statements/SHOW-CATALOG-RECYCLE-BIN"
+ "sql-manual/sql-reference/Show-Statements/SHOW-CATALOG-RECYCLE-BIN",
+ "sql-manual/sql-reference/Show-Statements/SHOW-QUERY-STATS"
]
},
{
@@ -1031,7 +1032,8 @@
"sql-manual/sql-reference/Utility-Statements/DESCRIBE",
"sql-manual/sql-reference/Utility-Statements/SWITCH",
"sql-manual/sql-reference/Utility-Statements/REFRESH",
- "sql-manual/sql-reference/Utility-Statements/SYNC"
+ "sql-manual/sql-reference/Utility-Statements/SYNC",
+ "sql-manual/sql-reference/Utility-Statements/CLEAN-QUERY-STATS"
]
}
]
@@ -1167,6 +1169,7 @@
"admin-manual/http-actions/fe/profile-action",
"admin-manual/http-actions/fe/query-detail-action",
"admin-manual/http-actions/fe/query-schema-action",
+ "admin-manual/http-actions/fe/query-stats-action",
"admin-manual/http-actions/fe/row-count-action",
"admin-manual/http-actions/fe/set-config-action",
"admin-manual/http-actions/fe/show-data-action",
diff --git a/docs/zh-CN/docs/admin-manual/config/fe-config.md b/docs/zh-CN/docs/admin-manual/config/fe-config.md
index 57612c7d26..fb440bfa1d 100644
--- a/docs/zh-CN/docs/admin-manual/config/fe-config.md
+++ b/docs/zh-CN/docs/admin-manual/config/fe-config.md
@@ -2710,4 +2710,16 @@ show data (其他用法:HELP SHOW DATA)
默认值:128
-用于限制parallel_fragment_exec_instance_num的设置,set parallel_fragment_exec_instance_num不能超过max_instance_num
\ No newline at end of file
+用于限制parallel_fragment_exec_instance_num的设置,set parallel_fragment_exec_instance_num不能超过max_instance_num
+
+#### `enable_query_hit_stats`
+
+
+
+默认值:false
+
+是否可以动态配置:true
+
+是否为 Master FE 节点独有的配置项:false
+
+控制是否启用查询命中率统计。默认为 false。
\ No newline at end of file
diff --git a/docs/zh-CN/docs/admin-manual/http-actions/fe/query-stats-action.md b/docs/zh-CN/docs/admin-manual/http-actions/fe/query-stats-action.md
new file mode 100644
index 0000000000..29a0297686
--- /dev/null
+++ b/docs/zh-CN/docs/admin-manual/http-actions/fe/query-stats-action.md
@@ -0,0 +1,100 @@
+---
+{
+ "title": "Query Stats Action",
+ "language": "zh-CN"
+}
+---
+
+
+
+# Query Stats Action
+
+
+
+## Request
+
+```
+查看
+get api/query_stats/
+get api/query_stats//
+get api/query_stats///
+
+清空
+delete api/query_stats//
+delete api/query_stats///
+```
+
+## Description
+
+获取或者删除指定的catalog 数据库或者表中的统计信息, 如果是doris catalog 可以使用default_cluster
+
+## Path parameters
+
+* ``
+
+ 指定的catalog 名称
+* ``
+
+ 指定的数据库名称
+* ``
+
+ 指定的表名称
+
+## Query parameters
+* `summary`
+如果为true 则只返回summary信息, 否则返回所有的表的详细统计信息,只在get 时使用
+
+## Request body
+
+```
+GET /api/query_stats/default_cluster/test_query_db/baseall?summary=false
+{
+ "msg": "success",
+ "code": 0,
+ "data": {
+ "summary": {
+ "query": 2
+ },
+ "detail": {
+ "baseall": {
+ "summary": {
+ "query": 2
+ }
+ }
+ }
+ },
+ "count": 0
+}
+
+```
+
+## Response
+
+* 返回结果集
+
+
+## Example
+
+
+2. 使用 curl 命令获取统计信息
+
+ ```
+ curl --location -u root: 'http://127.0.0.1:8030/api/query_stats/default_cluster/test_query_db/baseall?summary=false'
+ ```
diff --git a/docs/zh-CN/docs/sql-manual/sql-reference/Show-Statements/SHOW-QUERY-STATS.md b/docs/zh-CN/docs/sql-manual/sql-reference/Show-Statements/SHOW-QUERY-STATS.md
new file mode 100644
index 0000000000..af1b868323
--- /dev/null
+++ b/docs/zh-CN/docs/sql-manual/sql-reference/Show-Statements/SHOW-QUERY-STATS.md
@@ -0,0 +1,195 @@
+---
+{
+ "title": "SHOW-QUERY-STATS",
+ "language": "zh-CN"
+}
+---
+
+
+
+## SHOW-QUERY-STATS
+
+
+### Name
+
+SHOW QUERY STATS
+
+
+### Description
+
+该语句用于展示数据库中历史查询命中的库表列的情况
+
+```sql
+SHOW QUERY STATS [[FOR db_name]|[FROM table_name]] [ALL] [VERBOSE]];
+```
+
+说明:
+
+1. 支持查询数据库和表的历史查询命中情况,重启fe后数据会重置,每个fe单独统计
+2. 通过 FOR DATABASE 和FROM TABLE 可以指定查询数据库或者表的命中情况,后面分别接数据库名或者表名
+3. ALL 可以指定是否展示所有index的查询命中情况,VERBOSE 可以展示更详细的命中情况, 这两个参数可以单独使用,
+ 也可以一起使用,但是必须放在最后 而且只能用在表的查询上
+4. 如果没有 use 任何数据库那么直接执行`SHOW QUERY STATS` 将展示所有数据库的命中情况
+5. 命中结果中可能有两列:
+ QueryCount:该列被查询次数
+ FilterCount: 该列作为where 条件被查询的次数
+### Example
+
+1. 展示表`baseall` 的查询命中情况
+
+ ```sql
+ MySQL [test_query_db]> show query stats from baseall;
+ +-------+------------+-------------+
+ | Field | QueryCount | FilterCount |
+ +-------+------------+-------------+
+ | k0 | 0 | 0 |
+ | k1 | 0 | 0 |
+ | k2 | 0 | 0 |
+ | k3 | 0 | 0 |
+ | k4 | 0 | 0 |
+ | k5 | 0 | 0 |
+ | k6 | 0 | 0 |
+ | k10 | 0 | 0 |
+ | k11 | 0 | 0 |
+ | k7 | 0 | 0 |
+ | k8 | 0 | 0 |
+ | k9 | 0 | 0 |
+ | k12 | 0 | 0 |
+ | k13 | 0 | 0 |
+ +-------+------------+-------------+
+ 14 rows in set (0.002 sec)
+
+ MySQL [test_query_db]> select k0, k1,k2, sum(k3) from baseall where k9 > 1 group by k0,k1,k2;
+ +------+------+--------+-------------+
+ | k0 | k1 | k2 | sum(`k3`) |
+ +------+------+--------+-------------+
+ | 0 | 6 | 32767 | 3021 |
+ | 1 | 12 | 32767 | -2147483647 |
+ | 0 | 3 | 1989 | 1002 |
+ | 0 | 7 | -32767 | 1002 |
+ | 1 | 8 | 255 | 2147483647 |
+ | 1 | 9 | 1991 | -2147483647 |
+ | 1 | 11 | 1989 | 25699 |
+ | 1 | 13 | -32767 | 2147483647 |
+ | 1 | 14 | 255 | 103 |
+ | 0 | 1 | 1989 | 1001 |
+ | 0 | 2 | 1986 | 1001 |
+ | 1 | 15 | 1992 | 3021 |
+ +------+------+--------+-------------+
+ 12 rows in set (0.050 sec)
+
+ MySQL [test_query_db]> show query stats from baseall;
+ +-------+------------+-------------+
+ | Field | QueryCount | FilterCount |
+ +-------+------------+-------------+
+ | k0 | 1 | 0 |
+ | k1 | 1 | 0 |
+ | k2 | 1 | 0 |
+ | k3 | 1 | 0 |
+ | k4 | 0 | 0 |
+ | k5 | 0 | 0 |
+ | k6 | 0 | 0 |
+ | k10 | 0 | 0 |
+ | k11 | 0 | 0 |
+ | k7 | 0 | 0 |
+ | k8 | 0 | 0 |
+ | k9 | 1 | 1 |
+ | k12 | 0 | 0 |
+ | k13 | 0 | 0 |
+ +-------+------------+-------------+
+ 14 rows in set (0.001 sec)
+ ```
+
+2. 展示表的所物化视图的的命中的汇总情况
+
+ ```sql
+ MySQL [test_query_db]> show query stats from baseall all;
+ +-----------+------------+
+ | IndexName | QueryCount |
+ +-----------+------------+
+ | baseall | 1 |
+ +-----------+------------+
+ 1 row in set (0.005 sec)
+ ```
+
+3. 展示表的所物化视图的的命中的详细情况
+
+ ```sql
+ MySQL [test_query_db]> show query stats from baseall all verbose;
+ +-----------+-------+------------+-------------+
+ | IndexName | Field | QueryCount | FilterCount |
+ +-----------+-------+------------+-------------+
+ | baseall | k0 | 1 | 0 |
+ | | k1 | 1 | 0 |
+ | | k2 | 1 | 0 |
+ | | k3 | 1 | 0 |
+ | | k4 | 0 | 0 |
+ | | k5 | 0 | 0 |
+ | | k6 | 0 | 0 |
+ | | k10 | 0 | 0 |
+ | | k11 | 0 | 0 |
+ | | k7 | 0 | 0 |
+ | | k8 | 0 | 0 |
+ | | k9 | 1 | 1 |
+ | | k12 | 0 | 0 |
+ | | k13 | 0 | 0 |
+ +-----------+-------+------------+-------------+
+ 14 rows in set (0.017 sec)
+ ```
+
+4. 展示数据库的命中情况
+
+ ```sql
+ MySQL [test_query_db]> show query stats for test_query_db;
+ +----------------------------+------------+
+ | TableName | QueryCount |
+ +----------------------------+------------+
+ | compaction_tbl | 0 |
+ | bigtable | 0 |
+ | empty | 0 |
+ | tempbaseall | 0 |
+ | test | 0 |
+ | test_data_type | 0 |
+ | test_string_function_field | 0 |
+ | baseall | 1 |
+ | nullable | 0 |
+ +----------------------------+------------+
+ 9 rows in set (0.005 sec)
+ ```
+
+5. 展示所有数据库的命中情况,这时不能use 任何数据库
+
+ ```sql
+ MySQL [(none)]> show query stats;
+ +-----------------+------------+
+ | Database | QueryCount |
+ +-----------------+------------+
+ | test_query_db | 1 |
+ +-----------------+------------+
+ 1 rows in set (0.005 sec)
+ ```
+ SHOW QUERY STATS;
+ ```
+
+### Keywords
+
+ SHOW, QUERY, STATS;
+
+### Best Practice
diff --git a/docs/zh-CN/docs/sql-manual/sql-reference/Utility-Statements/CLEAN-QUERY-STATS.md b/docs/zh-CN/docs/sql-manual/sql-reference/Utility-Statements/CLEAN-QUERY-STATS.md
new file mode 100644
index 0000000000..0996c56328
--- /dev/null
+++ b/docs/zh-CN/docs/sql-manual/sql-reference/Utility-Statements/CLEAN-QUERY-STATS.md
@@ -0,0 +1,75 @@
+---
+{
+ "title": "CLEAN-QUERY-STATS",
+ "language": "zh-CN"
+}
+---
+
+
+
+## CLEAN-QUERY-STATS
+
+### Name
+
+
+CLEAN QUERY STATS
+
+
+### Description
+
+该语句用请空查询统计信息
+
+语法:
+
+```sql
+CLEAN [ALL| DATABASE | TABLE] QUERY STATS [[FOR db_name]|[FROM|IN] table_name]];
+```
+
+说明:
+
+1. 如果指定 ALL,则清空所有查询统计信息,包括数据库和表的统计信息,需要admin 权限
+2. 如果指定 DATABASE,则清空指定数据库的查询统计信息,需要对应database 的alter 权限
+3. 如果指定 TABLE,则清空指定表的查询统计信息,需要对应表的alter 权限
+
+### Example
+
+1. 清空所有统计信息
+
+ ```sql
+ clean all query stats;
+ ```
+
+2. 清空指定数据库的统计信息
+
+ ```sql
+ clean database query stats for test_query_db;
+ ```
+3. 清空指定表的统计信息
+
+ ```sql
+ clean table query stats from test_query_db.baseall;
+ ```
+
+### Keywords
+
+ CLEAN, QUERY, STATS
+
+### Best Practice
+
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index b52e4a0822..18d773b7e8 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -1902,7 +1902,7 @@ public class Config extends ConfigBase {
* This is to solve the case that user forgot the password.
*/
@ConfField(mutable = false)
- public static boolean skip_localhost_auth_check = false;
+ public static boolean skip_localhost_auth_check = true;
@ConfField(mutable = true)
public static boolean enable_round_robin_create_tablet = false;
@@ -1940,12 +1940,12 @@ public class Config extends ConfigBase {
@ConfField(mutable = true)
public static int max_instance_num = 128;
-
/**
* This config used for export/outfile.
* Whether delete all files in the directory specified by export/outfile.
* It is a very dangerous operation, should only be used in test env.
*/
+
@ConfField(mutable = false)
public static boolean enable_delete_existing_files = false;
/*
@@ -1961,4 +1961,13 @@ public class Config extends ConfigBase {
*/
@ConfField
public static long stats_cache_size = 10_0000;
+
+ /**
+ * This configuration is used to enable the statistics of query information, which will record
+ * the access status of databases, tables, and columns, and can be used to guide the
+ * optimization of table structures
+ *
+ */
+ @ConfField(mutable = true, masterOnly = false)
+ public static boolean enable_query_hit_stats = false;
}
diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup
index 28319e127b..a9f67cf270 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -1209,6 +1209,18 @@ clean_stmt ::=
{:
RESULT = new CleanProfileStmt();
:}
+ | KW_CLEAN KW_QUERY KW_STATS KW_FOR ident:db
+ {:
+ RESULT = new CleanQueryStatsStmt(db, CleanQueryStatsStmt.Scope.DB);
+ :}
+ | KW_CLEAN KW_ALL KW_QUERY KW_STATS
+ {:
+ RESULT = new CleanQueryStatsStmt();
+ :}
+ | KW_CLEAN KW_QUERY KW_STATS from_or_in table_name:tbl
+ {:
+ RESULT = new CleanQueryStatsStmt(tbl, CleanQueryStatsStmt.Scope.TABLE);
+ :}
;
// plugin statement
@@ -3977,6 +3989,26 @@ show_param ::=
| KW_CATALOG KW_RECYCLE KW_BIN opt_wild_where
{:
RESULT = new ShowCatalogRecycleBinStmt(parser.where);
+ :}
+ | KW_QUERY KW_STATS
+ {:
+ RESULT = new ShowQueryStatsStmt();
+ :}
+ | KW_QUERY KW_STATS KW_FOR ident:dbName
+ {:
+ RESULT = new ShowQueryStatsStmt(dbName);
+ :}
+ | KW_QUERY KW_STATS KW_FROM table_name:dbTblName
+ {:
+ RESULT = new ShowQueryStatsStmt(dbTblName, false, false);
+ :}
+ | KW_QUERY KW_STATS KW_FROM table_name:dbTblName KW_ALL
+ {:
+ RESULT = new ShowQueryStatsStmt(dbTblName, true, false);
+ :}
+ | KW_QUERY KW_STATS KW_FROM table_name:dbTblName KW_ALL KW_VERBOSE
+ {:
+ RESULT = new ShowQueryStatsStmt(dbTblName, true, true);
:}
;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java b/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java
index 38d195c6d8..358b6d4ccf 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/alter/MaterializedViewHandler.java
@@ -970,6 +970,13 @@ public class MaterializedViewHandler extends AlterHandler {
}
}
olapTable.deleteIndexInfo(mvName);
+ try {
+ Env.getCurrentEnv().getQueryStats().clear(Env.getCurrentInternalCatalog().getId(),
+ Env.getCurrentInternalCatalog().getDbOrDdlException(olapTable.getQualifiedDbName()).getId(),
+ olapTable.getId(), mvIndexId);
+ } catch (DdlException e) {
+ LOG.info("failed to clean stats for mv {} from {}", mvName, olapTable.getName(), e);
+ }
return mvIndexId;
}
@@ -993,9 +1000,11 @@ public class MaterializedViewHandler extends AlterHandler {
}
}
}
-
String rollupIndexName = olapTable.getIndexNameById(rollupIndexId);
olapTable.deleteIndexInfo(rollupIndexName);
+
+ env.getQueryStats().clear(env.getCurrentCatalog().getId(), db.getId(),
+ olapTable.getId(), rollupIndexId);
} finally {
olapTable.writeUnlock();
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CleanQueryStatsStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CleanQueryStatsStmt.java
new file mode 100644
index 0000000000..da53daa4be
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CleanQueryStatsStmt.java
@@ -0,0 +1,144 @@
+// 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.doris.analysis;
+
+import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.UserException;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.system.SystemInfoService;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * CLEAN ALL QUERY STATS;
+ * CLEAN DATABASE QUERY STATS FROM db;
+ * CLEAN TABLE QUERY STATS FROM db.table;
+ */
+public class CleanQueryStatsStmt extends DdlStmt {
+ private String dbName;
+ private TableName tableName;
+ private Scope scope;
+
+ /**
+ * CLEAN DATABASE QUERY STATS FROM db;
+ */
+ public CleanQueryStatsStmt(String dbName, Scope scope) {
+ this.dbName = dbName;
+ this.tableName = null;
+ this.scope = scope;
+ }
+
+ /**
+ * CLEAN TABLE QUERY STATS FROM db.table;
+ */
+ public CleanQueryStatsStmt(TableName tableName, Scope scope) {
+ this.dbName = null;
+ this.tableName = tableName;
+ this.scope = scope;
+ }
+
+ public CleanQueryStatsStmt() {
+ this.scope = Scope.ALL;
+ this.tableName = null;
+ this.dbName = null;
+ }
+
+ public String getDbName() {
+ return dbName;
+ }
+
+ public TableName getTableName() {
+ return tableName;
+ }
+
+ public Scope getScope() {
+ return scope;
+ }
+
+ @Override
+ public void analyze(Analyzer analyzer) throws UserException {
+ super.analyze(analyzer);
+ switch (scope) {
+ case ALL:
+ if (!Env.getCurrentEnv().getAccessManager()
+ .checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) {
+ ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
+ "CLEAN ALL QUERY STATS");
+ }
+ break;
+ case DB:
+ if (StringUtils.isEmpty(dbName)) {
+ dbName = analyzer.getDefaultDb();
+ }
+ if (StringUtils.isEmpty(dbName)) {
+ ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
+ }
+ dbName = ClusterNamespace.getFullName(SystemInfoService.DEFAULT_CLUSTER, dbName);
+
+ Env.getCurrentEnv().getCurrentCatalog().getDbOrAnalysisException(dbName);
+ if (!Env.getCurrentEnv().getAccessManager()
+ .checkDbPriv(ConnectContext.get(), dbName, PrivPredicate.ALTER)) {
+ ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
+ "CLEAN DATABASE QUERY STATS FOR " + ClusterNamespace.getNameFromFullName(dbName));
+ }
+ break;
+ case TABLE:
+ tableName.analyze(analyzer);
+ dbName = tableName.getDb();
+ if (StringUtils.isEmpty(dbName)) {
+ ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR);
+ }
+ DatabaseIf db = Env.getCurrentEnv().getCurrentCatalog().getDbOrAnalysisException(dbName);
+ db.getTableOrAnalysisException(tableName.getTbl());
+ if (!Env.getCurrentEnv().getAccessManager()
+ .checkTblPriv(ConnectContext.get(), dbName, tableName.getTbl(), PrivPredicate.ALTER)) {
+ ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR,
+ "CLEAN TABLE QUERY STATS FROM " + tableName);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + scope);
+ }
+ }
+
+ @Override
+ public String toSql() {
+ switch (scope) {
+ case ALL:
+ return "CLEAN ALL QUERY STATS";
+ case DB:
+ return "CLEAN DATABASE QUERY STATS FOR " + dbName;
+ case TABLE:
+ return "CLEAN TABLE QUERY STATS FROM " + tableName;
+ default:
+ throw new IllegalStateException("Unexpected value: " + scope);
+ }
+ }
+
+ /**
+ * Scope of clean query stats
+ */
+ public enum Scope {
+ ALL, DB, TABLE
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowQueryStatsStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowQueryStatsStmt.java
new file mode 100644
index 0000000000..9454160c7e
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowQueryStatsStmt.java
@@ -0,0 +1,234 @@
+// 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.doris.analysis;
+
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.UserException;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.ShowResultSetMetaData;
+import org.apache.doris.statistics.query.QueryStatsUtil;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class ShowQueryStatsStmt extends ShowStmt {
+
+ private static final ShowResultSetMetaData SHOW_QUERY_STATS_CATALOG_META_DATA = ShowResultSetMetaData.builder()
+ .addColumn(new Column("Database", ScalarType.createVarchar(20)))
+ .addColumn(new Column("QueryCount", ScalarType.createVarchar(30))).build();
+ private static final ShowResultSetMetaData SHOW_QUERY_STATS_DATABASE_META_DATA = ShowResultSetMetaData.builder()
+ .addColumn(new Column("TableName", ScalarType.createVarchar(20)))
+ .addColumn(new Column("QueryCount", ScalarType.createVarchar(30))).build();
+ private static final ShowResultSetMetaData SHOW_QUERY_STATS_TABLE_META_DATA = ShowResultSetMetaData.builder()
+ .addColumn(new Column("Field", ScalarType.createVarchar(20)))
+ .addColumn(new Column("QueryCount", ScalarType.createVarchar(30)))
+ .addColumn(new Column("FilterCount", ScalarType.createVarchar(30))).build();
+ private static final ShowResultSetMetaData SHOW_QUERY_STATS_TABLE_ALL_META_DATA = ShowResultSetMetaData.builder()
+ .addColumn(new Column("IndexName", ScalarType.createVarchar(20)))
+ .addColumn(new Column("QueryCount", ScalarType.createVarchar(30))).build();
+ private static final ShowResultSetMetaData SHOW_QUERY_STATS_TABLE_ALL_VERBOSE_META_DATA
+ = ShowResultSetMetaData.builder().addColumn(new Column("IndexName", ScalarType.createVarchar(20)))
+ .addColumn(new Column("Field", ScalarType.createVarchar(20)))
+ .addColumn(new Column("QueryCount", ScalarType.createVarchar(30)))
+ .addColumn(new Column("FilterCount", ScalarType.createVarchar(30))).build();
+
+ TableName tableName;
+ String dbName;
+ boolean all;
+ boolean verbose;
+ List> totalRows;
+ ShowQueryStatsType type;
+
+ /**
+ * for SHOW QUERY STATS FROM TABLE
+ */
+ public ShowQueryStatsStmt(TableName tableName, boolean all, boolean verbose) {
+ this.tableName = tableName;
+ this.all = all;
+ this.verbose = verbose;
+ this.totalRows = Lists.newArrayList();
+ }
+
+ /**
+ * for SHOW QUERY STATS FROM DATABASE
+ */
+ public ShowQueryStatsStmt(String dbName) {
+ this.tableName = null;
+ this.all = false;
+ this.verbose = false;
+ this.dbName = dbName;
+ this.totalRows = Lists.newArrayList();
+ this.type = ShowQueryStatsType.DATABASE;
+ }
+
+ /**
+ * for SHOW QUERY STATS
+ */
+ public ShowQueryStatsStmt() {
+ this.tableName = null;
+ this.all = false;
+ this.verbose = false;
+ this.dbName = null;
+ this.totalRows = Lists.newArrayList();
+ this.type = ShowQueryStatsType.CATALOG;
+ }
+
+ @Override
+ public void analyze(Analyzer analyzer) throws UserException {
+ super.analyze(analyzer);
+ if (StringUtils.isEmpty(dbName)) {
+ dbName = analyzer.getDefaultDb();
+ type = ShowQueryStatsType.DATABASE;
+ }
+ if (Strings.isNullOrEmpty(dbName)) {
+ dbName = analyzer.getDefaultDb();
+ } else {
+ dbName = ClusterNamespace.getFullName(getClusterName(), dbName);
+ }
+ String catalog = Env.getCurrentEnv().getCurrentCatalog().getName();
+ if (tableName == null && StringUtils.isEmpty(dbName)) {
+ if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) {
+ ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "SHOW QUERY STATS");
+ }
+ Map result = QueryStatsUtil.getMergedCatalogStats(catalog);
+ result.forEach((dbName, queryHit) -> {
+ totalRows.add(Arrays.asList(dbName, String.valueOf(queryHit)));
+ });
+ type = ShowQueryStatsType.CATALOG;
+ return;
+ }
+ if (tableName != null) {
+ tableName.analyze(analyzer);
+ dbName = tableName.getDb();
+ }
+ Database db = (Database) Env.getCurrentEnv().getCurrentCatalog().getDbOrDdlException(dbName);
+ if (tableName != null) {
+ db.getTableOrDdlException(tableName.getTbl());
+ }
+ if (tableName == null) {
+ Map stats = QueryStatsUtil.getMergedDatabaseStats(catalog, dbName);
+ stats.forEach((tableName, queryHit) -> {
+ if (Env.getCurrentEnv().getAccessManager()
+ .checkTblPriv(ConnectContext.get(), dbName, tableName, PrivPredicate.SHOW)) {
+ totalRows.add(Arrays.asList(tableName, String.valueOf(queryHit)));
+ }
+ });
+ } else {
+ if (!Env.getCurrentEnv().getAccessManager()
+ .checkTblPriv(ConnectContext.get(), tableName, PrivPredicate.SHOW)) {
+ ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "SHOW QUERY STATS",
+ ConnectContext.get().getQualifiedUser(), ConnectContext.get().getRemoteIP(),
+ dbName + ": " + tableName);
+ }
+ if (all && verbose) {
+ type = ShowQueryStatsType.TABLE_ALL_VERBOSE;
+ QueryStatsUtil.getMergedTableAllVerboseStats(catalog, dbName, tableName.getTbl())
+ .forEach((indexName, stat) -> {
+ final boolean[] firstRow = new boolean[] {true};
+ stat.forEach((col, statCount) -> {
+ totalRows.add(Arrays.asList(firstRow[0] ? indexName : "", col,
+ String.valueOf(statCount.first), String.valueOf(statCount.second)));
+ firstRow[0] = false;
+ });
+ });
+ } else if (all) {
+ type = ShowQueryStatsType.TABLE_ALL;
+
+ Map stats = QueryStatsUtil.getMergedTableAllStats(catalog, dbName, tableName.getTbl());
+ stats.forEach((indexName, queryHit) -> {
+ totalRows.add(Arrays.asList(indexName, String.valueOf(queryHit)));
+ });
+ } else if (verbose) {
+ Preconditions.checkState(false, "verbose is not supported if all is not set");
+ } else {
+ type = ShowQueryStatsType.TABLE;
+ QueryStatsUtil.getMergedTableStats(catalog, dbName, tableName.getTbl()).forEach((col, statCount) -> {
+ totalRows.add(
+ Arrays.asList(col, String.valueOf(statCount.first), String.valueOf(statCount.second)));
+ });
+ }
+ }
+ }
+
+ @Override
+ public String toSql() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("SHOW QUERY STATS");
+ if (tableName != null) {
+ builder.append(" FROM ").append(tableName.toSql());
+ } else if (StringUtils.isNotEmpty(dbName)) {
+ builder.append(" FROM ").append("`").append(dbName).append("`");
+ }
+ if (all) {
+ builder.append(" ALL");
+ }
+ if (verbose) {
+ builder.append(" VERBOSE");
+ }
+ return builder.toString();
+ }
+
+ public List> getResultRows() throws AnalysisException {
+ return totalRows;
+ }
+
+ @Override
+ public ShowResultSetMetaData getMetaData() {
+ switch (type) {
+ case CATALOG:
+ return SHOW_QUERY_STATS_CATALOG_META_DATA;
+ case DATABASE:
+ return SHOW_QUERY_STATS_DATABASE_META_DATA;
+ case TABLE:
+ return SHOW_QUERY_STATS_TABLE_META_DATA;
+ case TABLE_ALL:
+ return SHOW_QUERY_STATS_TABLE_ALL_META_DATA;
+ case TABLE_ALL_VERBOSE:
+ return SHOW_QUERY_STATS_TABLE_ALL_VERBOSE_META_DATA;
+ default:
+ Preconditions.checkState(false);
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return toSql();
+ }
+
+ /**
+ * Show query statistics type
+ */
+ public enum ShowQueryStatsType {
+ CATALOG, DATABASE, TABLE, TABLE_ALL, TABLE_ALL_VERBOSE
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowTabletStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowTabletStmt.java
index 2666dafedc..6ea93fa002 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowTabletStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowTabletStmt.java
@@ -315,6 +315,7 @@ public class ShowTabletStmt extends ShowStmt {
builder.addColumn(new Column("IndexId", ScalarType.createVarchar(30)));
builder.addColumn(new Column("IsSync", ScalarType.createVarchar(30)));
builder.addColumn(new Column("Order", ScalarType.createVarchar(30)));
+ builder.addColumn(new Column("QueryHits", ScalarType.createVarchar(30)));
builder.addColumn(new Column("DetailCmd", ScalarType.createVarchar(30)));
} else {
for (String title : TabletsProcDir.TITLE_NAMES) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/backup/BackupJob.java b/fe/fe-core/src/main/java/org/apache/doris/backup/BackupJob.java
index 3be065af44..2300cd1e01 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/backup/BackupJob.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/backup/BackupJob.java
@@ -776,8 +776,7 @@ public class BackupJob extends AbstractJob {
Collections.sort(replicaIds);
for (Long replicaId : replicaIds) {
Replica replica = tablet.getReplicaById(replicaId);
- if (replica.getLastFailedVersion() < 0 && (replica.getVersion() > visibleVersion
- || (replica.getVersion() == visibleVersion))) {
+ if (replica.getLastFailedVersion() < 0 && replica.getVersion() >= visibleVersion) {
return replica;
}
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 0edd2536fd..e853abf798 100755
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -172,6 +172,7 @@ import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.persist.AlterMultiMaterializedView;
import org.apache.doris.persist.BackendReplicasInfo;
import org.apache.doris.persist.BackendTabletsInfo;
+import org.apache.doris.persist.CleanQueryStatsInfo;
import org.apache.doris.persist.DropPartitionInfo;
import org.apache.doris.persist.EditLog;
import org.apache.doris.persist.GlobalVarPersistInfo;
@@ -211,6 +212,7 @@ import org.apache.doris.statistics.AnalysisTaskScheduler;
import org.apache.doris.statistics.StatisticsAutoAnalyzer;
import org.apache.doris.statistics.StatisticsCache;
import org.apache.doris.statistics.StatisticsCleaner;
+import org.apache.doris.statistics.query.QueryStats;
import org.apache.doris.system.Backend;
import org.apache.doris.system.Frontend;
import org.apache.doris.system.HeartbeatMgr;
@@ -438,6 +440,8 @@ public class Env {
private ResourceGroupMgr resourceGroupMgr;
+ private QueryStats queryStats;
+
private StatisticsCleaner statisticsCleaner;
/**
@@ -652,6 +656,7 @@ public class Env {
}
this.globalFunctionMgr = new GlobalFunctionMgr();
this.resourceGroupMgr = new ResourceGroupMgr();
+ this.queryStats = new QueryStats();
this.loadManagerAdapter = new LoadManagerAdapter();
this.hiveTransactionMgr = new HiveTransactionMgr();
}
@@ -4183,6 +4188,9 @@ public class Env {
if (column != null) {
column.setName(newColName);
hasColumn = true;
+ Env.getCurrentEnv().getQueryStats()
+ .rename(Env.getCurrentEnv().getCurrentCatalog().getId(), db.getId(),
+ table.getId(), entry.getKey(), colName, newColName);
}
}
if (!hasColumn) {
@@ -5235,7 +5243,6 @@ public class Env {
return analysisManager;
}
-
public GlobalFunctionMgr getGlobalFunctionMgr() {
return globalFunctionMgr;
}
@@ -5251,4 +5258,13 @@ public class Env {
public StatisticsAutoAnalyzer getStatisticsAutoAnalyzer() {
return statisticsAutoAnalyzer;
}
+
+ public QueryStats getQueryStats() {
+ return queryStats;
+ }
+
+ public void cleanQueryStats(CleanQueryStatsInfo info) throws DdlException {
+ queryStats.clear(info);
+ editLog.logCleanQueryStats(info);
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/proc/ReplicasProcNode.java b/fe/fe-core/src/main/java/org/apache/doris/common/proc/ReplicasProcNode.java
index 82bbb2618c..383ba14ab0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/ReplicasProcNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/ReplicasProcNode.java
@@ -22,8 +22,10 @@ import org.apache.doris.catalog.OlapTable;
import org.apache.doris.catalog.Replica;
import org.apache.doris.catalog.Tablet;
import org.apache.doris.catalog.TabletMeta;
+import org.apache.doris.common.Config;
import org.apache.doris.common.util.NetUtils;
import org.apache.doris.common.util.TimeUtils;
+import org.apache.doris.statistics.query.QueryStatsUtil;
import org.apache.doris.system.Backend;
import com.google.common.collect.ImmutableList;
@@ -41,7 +43,7 @@ public class ReplicasProcNode implements ProcNodeInterface {
.add("BackendId").add("Version").add("LstSuccessVersion").add("LstFailedVersion").add("LstFailedTime")
.add("SchemaHash").add("LocalDataSize").add("RemoteDataSize").add("RowCount").add("State").add("IsBad")
.add("VersionCount").add("PathHash").add("MetaUrl").add("CompactionStatus").add("CooldownReplicaId")
- .add("CooldownMetaId").build();
+ .add("CooldownMetaId").add("QueryHits").build();
private long tabletId;
private List replicas;
@@ -85,6 +87,10 @@ public class ReplicasProcNode implements ProcNodeInterface {
if (replica.getCooldownMetaId() != null) {
cooldownMetaId = replica.getCooldownMetaId().toString();
}
+ long queryHits = 0L;
+ if (Config.enable_query_hit_stats) {
+ queryHits = QueryStatsUtil.getMergedReplicaStats(replica.getId());
+ }
result.addRow(Arrays.asList(String.valueOf(replica.getId()),
String.valueOf(replica.getBackendId()),
String.valueOf(replica.getVersion()),
@@ -102,7 +108,8 @@ public class ReplicasProcNode implements ProcNodeInterface {
metaUrl,
compactionUrl,
String.valueOf(tablet.getCooldownConf().first),
- cooldownMetaId));
+ cooldownMetaId,
+ String.valueOf(queryHits)));
}
return result;
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/proc/TabletsProcDir.java b/fe/fe-core/src/main/java/org/apache/doris/common/proc/TabletsProcDir.java
index 47a1790cfa..e13d1846a5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/TabletsProcDir.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/TabletsProcDir.java
@@ -24,10 +24,12 @@ import org.apache.doris.catalog.Table;
import org.apache.doris.catalog.Tablet;
import org.apache.doris.catalog.TabletInvertedIndex;
import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.util.ListComparator;
import org.apache.doris.common.util.NetUtils;
import org.apache.doris.common.util.TimeUtils;
+import org.apache.doris.statistics.query.QueryStatsUtil;
import org.apache.doris.system.Backend;
import com.google.common.base.Preconditions;
@@ -36,7 +38,9 @@ import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/*
* SHOW PROC /dbs/dbId/tableId/partitions/partitionId/indexId
@@ -48,8 +52,8 @@ public class TabletsProcDir implements ProcDirInterface {
.add("LstSuccessVersion").add("LstFailedVersion").add("LstFailedTime")
.add("LocalDataSize").add("RemoteDataSize").add("RowCount").add("State")
.add("LstConsistencyCheckTime").add("CheckVersion")
- .add("VersionCount").add("PathHash").add("MetaUrl").add("CompactionStatus").add("CooldownReplicaId")
- .add("CooldownMetaId").build();
+ .add("VersionCount").add("QueryHits").add("PathHash").add("MetaUrl").add("CompactionStatus")
+ .add("CooldownReplicaId").add("CooldownMetaId").build();
private Table table;
private MaterializedIndex index;
@@ -67,6 +71,16 @@ public class TabletsProcDir implements ProcDirInterface {
List> tabletInfos = new ArrayList>();
table.readLock();
try {
+ Map replicaIdToQueryHits = new HashMap<>();
+ if (Config.enable_query_hit_stats) {
+ List replicaIds = new ArrayList();
+ for (Tablet tablet : index.getTablets()) {
+ for (Replica replica : tablet.getReplicas()) {
+ replicaIds.add(replica.getId());
+ }
+ }
+ replicaIdToQueryHits = QueryStatsUtil.getMergedReplicasStats(replicaIds);
+ }
// get infos
for (Tablet tablet : index.getTablets()) {
long tabletId = tablet.getId();
@@ -92,6 +106,7 @@ public class TabletsProcDir implements ProcDirInterface {
tabletInfo.add(-1); // check version
tabletInfo.add(-1); // check version hash
tabletInfo.add(-1); // version count
+ tabletInfo.add(0L); // query hits
tabletInfo.add(-1); // path hash
tabletInfo.add(FeConstants.null_string); // meta url
tabletInfo.add(FeConstants.null_string); // compaction status
@@ -124,6 +139,7 @@ public class TabletsProcDir implements ProcDirInterface {
tabletInfo.add(TimeUtils.longToTimeString(tablet.getLastCheckTime()));
tabletInfo.add(tablet.getCheckedVersion());
tabletInfo.add(replica.getVersionCount());
+ tabletInfo.add(replicaIdToQueryHits.getOrDefault(replica.getId(), 0L));
tabletInfo.add(replica.getPathHash());
Backend be = backendMap.get(replica.getBackendId());
String host = (be == null ? Backend.DUMMY_IP : be.getHost());
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java
index 6b073b05e2..0fbc19596d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/Util.java
@@ -49,6 +49,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.function.LongUnaryOperator;
import java.util.function.Predicate;
public class Util {
@@ -92,6 +93,21 @@ public class Util {
TYPE_STRING_MAP.put(PrimitiveType.NULL_TYPE, "null");
}
+ public static LongUnaryOperator overflowSafeIncrement() {
+ return original -> {
+ if (original == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ }
+ long r = original + 1;
+ if (r == Long.MAX_VALUE || ((original ^ r) & (1 ^ r)) < 0) {
+ // unbounded reached
+ return Long.MAX_VALUE;
+ } else {
+ return r;
+ }
+ };
+ }
+
private static class CmdWorker extends Thread {
private final Process process;
private Integer exitValue;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java
index 566b1d455d..2c400786e4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java
@@ -132,6 +132,7 @@ public class CatalogMgr implements Writable, GsonPostProcessable {
catalogResource.removeReference(catalog.getName(), ReferenceType.CATALOG);
}
}
+ Env.getCurrentEnv().getQueryStats().clear(catalog.getId());
}
return catalog;
}
@@ -158,6 +159,20 @@ public class CatalogMgr implements Writable, GsonPostProcessable {
return idToCatalog.get(id);
}
+ public CatalogIf getCatalogOrAnalysisException(long id) throws AnalysisException {
+ return getCatalogOrException(id,
+ catalog -> new AnalysisException(ErrorCode.ERR_UNKNOWN_CATALOG.formatErrorMsg(catalog),
+ ErrorCode.ERR_UNKNOWN_CATALOG));
+ }
+
+ public CatalogIf getCatalogOrException(long id, Function e) throws E {
+ CatalogIf catalog = idToCatalog.get(id);
+ if (catalog == null) {
+ throw e.apply(id);
+ }
+ return catalog;
+ }
+
public CatalogIf getCatalogOrException(String name, Function e) throws E {
CatalogIf catalog = nameToCatalog.get(name);
if (catalog == null) {
@@ -277,6 +292,8 @@ public class CatalogMgr implements Writable, GsonPostProcessable {
Env.getCurrentEnv().getEditLog().logCatalogLog(OperationType.OP_DROP_CATALOG, log);
lastDBOfCatalog.remove(stmt.getCatalogName());
+ Env.getCurrentEnv().getQueryStats().clear(catalog.getId());
+
} finally {
writeUnlock();
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
index afade5022e..9713341a23 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
@@ -533,6 +533,7 @@ public class InternalCatalog implements CatalogIf {
fullNameToDb.remove(db.getFullName());
DropDbInfo info = new DropDbInfo(dbName, stmt.isForceDrop(), recycleTime);
Env.getCurrentEnv().getEditLog().logDropDb(info);
+ Env.getCurrentEnv().getQueryStats().clear(Env.getCurrentEnv().getCurrentCatalog().getId(), db.getId());
} finally {
unlock();
}
@@ -574,6 +575,7 @@ public class InternalCatalog implements CatalogIf {
} else {
Env.getCurrentEnv().eraseDatabase(db.getId(), false);
}
+ Env.getCurrentEnv().getQueryStats().clear(Env.getCurrentEnv().getInternalCatalog().getId(), db.getId());
} finally {
db.writeUnlock();
}
@@ -865,6 +867,8 @@ public class InternalCatalog implements CatalogIf {
}
DropInfo info = new DropInfo(db.getId(), table.getId(), -1L, stmt.isForceDrop(), recycleTime);
Env.getCurrentEnv().getEditLog().logDropTable(info);
+ Env.getCurrentEnv().getQueryStats().clear(Env.getCurrentEnv().getCurrentCatalog().getId(),
+ db.getId(), table.getId());
} finally {
db.writeUnlock();
}
@@ -910,6 +914,8 @@ public class InternalCatalog implements CatalogIf {
table.writeLock();
try {
unprotectDropTable(db, table, isForceDrop, true, recycleTime);
+ Env.getCurrentEnv().getQueryStats().clear(Env.getCurrentInternalCatalog().getId(), db.getId(),
+ tableId);
} finally {
table.writeUnlock();
db.writeUnlock();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/QueryStatsAction.java b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/QueryStatsAction.java
new file mode 100644
index 0000000000..7656fbb0a5
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/QueryStatsAction.java
@@ -0,0 +1,292 @@
+// 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.doris.httpv2.rest;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.cluster.ClusterNamespace;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.system.SystemInfoService;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This class is used to get query stats or clear query stats.
+ */
+@RestController
+public class QueryStatsAction extends RestBaseController {
+ private static final Logger LOG = LogManager.getLogger(QueryStatsAction.class);
+
+ @RequestMapping(path = "/api/query_stats/{catalog}", method = RequestMethod.GET)
+ protected Object getQueryStatsFromCatalog(@PathVariable("catalog") String catalog,
+ @RequestParam(name = "summary", required = false, defaultValue = "true") boolean summary,
+ @RequestParam(name = "pretty", required = false, defaultValue = "false") boolean pretty,
+ HttpServletRequest request, HttpServletResponse response) {
+ if (pretty && summary) {
+ return ResponseEntityBuilder.badRequest("pretty and summary can not be true at the same time");
+ }
+ executeCheckPassword(request, response);
+ checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN);
+ // use NS_KEY as catalog, but NS_KEY's default value is 'default_cluster'.
+ if (catalog.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) {
+ catalog = InternalCatalog.INTERNAL_CATALOG_NAME;
+ }
+ try {
+ Map result = Env.getCurrentEnv().getQueryStats().getStats(catalog, summary);
+ if (pretty) {
+ return ResponseEntityBuilder.ok(getPrettyJson(result.get("detail"), QueryStatsType.DATABASE));
+ }
+ return ResponseEntityBuilder.ok(result);
+ } catch (Exception e) {
+ LOG.warn("get query stats from catalog {} failed", catalog, e);
+ return ResponseEntityBuilder.internalError(e.getMessage());
+ }
+ }
+
+ @RequestMapping(path = "/api/query_stats/{catalog}/{database}", method = RequestMethod.GET)
+ protected Object getQueryStatsFromDatabase(@PathVariable("catalog") String catalog,
+ @PathVariable("database") String database,
+ @RequestParam(name = "summary", required = false, defaultValue = "true") boolean summary,
+ @RequestParam(name = "pretty", required = false, defaultValue = "false") boolean pretty,
+ HttpServletRequest request, HttpServletResponse response) {
+ if (pretty && summary) {
+ return ResponseEntityBuilder.badRequest("pretty and summary can not be true at the same time");
+ }
+ executeCheckPassword(request, response);
+ checkDbAuth(ConnectContext.get().getCurrentUserIdentity(), database, PrivPredicate.SHOW);
+ // use NS_KEY as catalog, but NS_KEY's default value is 'default_cluster'.
+ String clasterName = catalog;
+ if (catalog.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) {
+ catalog = InternalCatalog.INTERNAL_CATALOG_NAME;
+ }
+ database = ClusterNamespace.getFullName(clasterName, database);
+ try {
+ Map result = Env.getCurrentEnv().getQueryStats().getStats(catalog, database, summary);
+ if (pretty) {
+ return ResponseEntityBuilder.ok(getPrettyJson(result.get("detail"), QueryStatsType.TABLE));
+ }
+ return ResponseEntityBuilder.ok(result);
+ } catch (Exception e) {
+ LOG.warn("get query stats from catalog {} failed", catalog, e);
+ return ResponseEntityBuilder.internalError(e.getMessage());
+ }
+ }
+
+ @RequestMapping(path = "/api/query_stats/{catalog}/{database}/{table}", method = RequestMethod.GET)
+ protected Object getQueryStatsFromTable(@PathVariable("catalog") String catalog,
+ @PathVariable("database") String database, @PathVariable("table") String table,
+ @RequestParam(name = "summary", required = false, defaultValue = "true") boolean summary,
+ @RequestParam(name = "pretty", required = false, defaultValue = "false") boolean pretty,
+ HttpServletRequest request, HttpServletResponse response) {
+ if (pretty && summary) {
+ return ResponseEntityBuilder.badRequest("pretty and summary can not be true at the same time");
+ }
+ executeCheckPassword(request, response);
+ checkTblAuth(ConnectContext.get().getCurrentUserIdentity(), database, table, PrivPredicate.SHOW);
+ // use NS_KEY as catalog, but NS_KEY's default value is 'default_cluster'.
+ String clasterName = catalog;
+ if (catalog.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) {
+ catalog = InternalCatalog.INTERNAL_CATALOG_NAME;
+ }
+ database = ClusterNamespace.getFullName(clasterName, database);
+ try {
+ Map result = Env.getCurrentEnv().getQueryStats().getStats(catalog, database, table, summary);
+ if (pretty) {
+ return ResponseEntityBuilder.ok(getPrettyJson(result.get("detail"), QueryStatsType.INDEX));
+ }
+ return ResponseEntityBuilder.ok(result);
+ } catch (Exception e) {
+ LOG.warn("get query stats from catalog {} failed", catalog, e);
+ return ResponseEntityBuilder.internalError(e.getMessage());
+ }
+ }
+
+ @RequestMapping(path = "/api/query_stats/{catalog}/{database}/{table}/{index}", method = RequestMethod.GET)
+ protected Object getQueryStatsFromIndex(@PathVariable("catalog") String catalog,
+ @PathVariable("database") String database, @PathVariable("table") String table,
+ @PathVariable("index") String index,
+ @RequestParam(name = "summary", required = false, defaultValue = "true") boolean summary,
+ @RequestParam(name = "pretty", required = false, defaultValue = "false") boolean pretty,
+ HttpServletRequest request, HttpServletResponse response) {
+ if (pretty && summary) {
+ return ResponseEntityBuilder.badRequest("pretty and summary can not be true at the same time");
+ }
+ executeCheckPassword(request, response);
+ checkTblAuth(ConnectContext.get().getCurrentUserIdentity(), database, table, PrivPredicate.SHOW);
+ // use NS_KEY as catalog, but NS_KEY's default value is 'default_cluster'.
+ String clasterName = catalog;
+ if (catalog.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) {
+ catalog = InternalCatalog.INTERNAL_CATALOG_NAME;
+ }
+ database = ClusterNamespace.getFullName(clasterName, database);
+ try {
+ Map result = Env.getCurrentEnv().getQueryStats()
+ .getStats(catalog, database, table, index, summary);
+ if (pretty) {
+ return ResponseEntityBuilder.ok(getPrettyJson(result.get("detail"), QueryStatsType.COLUMN));
+ }
+ return ResponseEntityBuilder.ok(result);
+ } catch (Exception e) {
+ LOG.warn("get query stats from catalog {} failed", catalog, e);
+ return ResponseEntityBuilder.internalError(e.getMessage());
+ }
+ }
+
+ @RequestMapping(path = "/api/query_stats/{catalog}/{database}", method = RequestMethod.DELETE)
+ protected Object clearQueryStatsFromDatabase(@PathVariable("catalog") String catalog,
+ @PathVariable("database") String database, HttpServletRequest request, HttpServletResponse response) {
+ executeCheckPassword(request, response);
+ checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN);
+ // use NS_KEY as catalog, but NS_KEY's default value is 'default_cluster'.
+ String clasterName = catalog;
+ if (catalog.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) {
+ catalog = InternalCatalog.INTERNAL_CATALOG_NAME;
+ }
+ database = ClusterNamespace.getFullName(clasterName, database);
+ Env.getCurrentEnv().getQueryStats().clear(catalog, database);
+ return ResponseEntityBuilder.ok();
+ }
+
+ @RequestMapping(path = "/api/query_stats/{catalog}/{database}/{table}", method = RequestMethod.DELETE)
+ protected Object clearQueryStatsFromTable(@PathVariable("catalog") String catalog,
+ @PathVariable("database") String database, @PathVariable("table") String table, HttpServletRequest request,
+ HttpServletResponse response) {
+ executeCheckPassword(request, response);
+ checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN);
+ // use NS_KEY as catalog, but NS_KEY's default value is 'default_cluster'.
+ String clasterName = catalog;
+ if (catalog.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) {
+ catalog = InternalCatalog.INTERNAL_CATALOG_NAME;
+ }
+ database = ClusterNamespace.getFullName(clasterName, database);
+ Env.getCurrentEnv().getQueryStats().clear(catalog, database, table);
+ return ResponseEntityBuilder.ok();
+ }
+
+ private JSONArray getPrettyJson(Map stats, QueryStatsType type) {
+ JSONArray result = new JSONArray();
+ QueryStatsType nextType = QueryStatsType.INVALID;
+ switch (type) {
+ case CATALOG:
+ nextType = QueryStatsType.DATABASE;
+ break;
+ case DATABASE:
+ nextType = QueryStatsType.TABLE;
+ break;
+ case TABLE:
+ nextType = QueryStatsType.INDEX;
+ break;
+ case INDEX:
+ nextType = QueryStatsType.COLUMN;
+ break;
+ case COLUMN:
+ nextType = QueryStatsType.DETAIL;
+ break;
+ default:
+ break;
+ }
+ for (Map.Entry entry : stats.entrySet()) {
+ JSONObject obj = new JSONObject();
+ if (type == QueryStatsType.COLUMN) {
+ obj.put("name", entry.getKey());
+ obj.put("type", type.toString());
+ Map columnStats = (Map) entry.getValue();
+ obj.put("value",
+ Math.max(columnStats.getOrDefault("query", 0L), columnStats.getOrDefault("filter", 0L)));
+ JSONArray children = new JSONArray();
+ BiFunction