更新时间:2026-05-22 GMT+08:00
分享

使用查询过滤器最佳实践

场景介绍

在企业级数据库业务开发与运维过程中,SQL语句的质量直接决定了数据库的性能稳定性、数据安全性以及业务系统的响应效率。

随着业务规模扩大、开发团队人员流动、业务场景复杂化,非标准化、低性能、高风险的劣质 SQL会频繁出现,且难以通过人工审核完全规避:

  1. 开发人员因对数据库底层原理不熟悉、业务迭代紧急等原因,可能写出关联表过多、未加索引的全表扫描、大事务查询、非法权限访问等 SQL。
  2. 这类SQL 一旦上线执行,轻则导致数据库CPU/IO使用率飙升、查询响应超时,影响正常业务流程;重则引发数据库锁表、事务阻塞,甚至造成数据泄露或误删改,带来不可挽回的损失。
  3. 传统的事后排查(如 SQL慢查询日志分析)存在滞后性,往往是慢SQL已经引发性能问题后才被发现,无法提前规避风险。
  4. 人工代码评审难以覆盖所有场景,尤其在多团队协作、高频迭代的场景下,效率低下且容易遗漏。

为解决上述痛点,DWS查询过滤器应运而生。它是一种前置性SQL管控机制,允许运维人员或DBA提前识别劣质SQL的核心特征(如表关联数量、SQL 类型、执行时长阈值、权限范围等),通过DDL语句将这些特征抽象为可落地的过滤规则并存储在数据库中

当业务系统提交 SQL语句请求执行时,数据库会先触发过滤规则校验,匹配到劣质SQL特征的请求会被直接拦截并返回报错信息,只有通过校验的合法SQL才能进入执行流程,从而实现 “防患于未然” 的SQL质量管控。

约束限制

该功能仅9.1.0.100及以上集群版本支持。

前提条件

如果是普通用户无法创建查询过滤规则,需要通过dbadmin系统管理员按以下语法进行授权。建议创建查询过滤规则时尽量缩小适用范围,避免误过滤,或者范围过大导致性能劣化。

1
GRANT gs_role_block TO user;

创建查询过滤规则

创建查询过滤规则支持两种方式:

  • 控制台方式,参见使用DWS查询过滤器拦截慢SQL章节。
  • DDL语法创建方式,语法格式如下,详细解释参见CREATE BLOCK RULE
    1
    2
    3
    4
    5
    6
    CREATE BLOCK RULE [ IF NOT EXISTS ] block_name
        [ [ TO user_name@'host' ] | [ TO user_name ] | [ TO 'host' ] ] |
        [ FOR UPDATE | SELECT | INSERT | DELETE | MERGE ] |
        FILTER BY
        { SQL ( 'text' ) | TEMPLATE ( template_parameter = value ) }
        [ WITH ( { with_parameter = value }, [, ... ] ) ];
    

    举例,在业务开发过程中,要想禁止对2张以上的表进行关联查询,此时可以使用DDL语句创建过滤规则:

    1
    2
    3
    4
    CREATE TABLE test_block_rule1 (c1 int,c2 int);
    CREATE TABLE test_block_rule2 (c1 int,c2 int);
    CREATE TABLE test_block_rule3 (c1 int,c2 int);
    CREATE BLOCK RULE forbid_2_t_sel FOR SELECT FILTER BY  SQL('test_block_rule') WITH(table_num='2');
    

    table_num指的是一个语句中出现的表的个数,此时所有查询语句不能包含有两张表以上的查询。

    1
    2
    3
    ----两张表直接关联查询,可以正常执行
    SET enable_fast_query_shipping=off;
    SELECT * FROM test_block_rule1 t1 JOIN test_block_rule2 t2 ON t1.c1=t2.c2;
    

    1
    2
    ----三张表直接关联查询,被拦截
    SELECT * FROM test_block_rule1 t1 JOIN test_block_rule2 t2 ON t1.c1=t2.c2 JOIN test_block_rule3 t3 ON t2.c1=t3.c1;
    

    所有选项均支持二次修改,如果需要去除部分字段的限制,可以指定default关键词,例如:

    1
    2
    3
    4
    --修改为只能查询1张表
    ALTER BLOCK RULE forbid_2_t_sel with(table_num='1');
    SELECT * FROM test_block_rule1 t1 JOIN test_block_rule2 t2 ON t1.c1=t2.c2;
    SELECT * FROM test_block_rule1 t1;
    

    1
    2
    3
    4
    --去除查询中表个数的限制
    ALTER BLOCK RULE forbid_2_t_sel with(table_num=default);
    --再次查询报错拦截
    SELECT * FROM test_block_rule1 t1;
    

如果想查看或者导入查询过滤规则的定义,可以通过pg_get_blockruledef进行查询。

1
SELECT * FROM pg_get_blockruledef('forbid_2_t_sel');

所有的查询过滤规则元数据全部保存在pg_blocklists系统表中,可以通过查看系统表浏览所有的查询过滤规则。

1
gaussdb=# SELECT * FROM pg_blocklists;

使用关键词进行查询过滤

CREATE BLOCK RULE bl1
To block_user
FOR SELECT
FILTER BY SQL ('tt')
WITH(partition_num='2',
     table_num='1',
     estimate_row='5'
     );

SELECT * FROM tt;
ERROR:  hit block rule bl1(user_name: block_user, block_type: SELECT, regexp_sql: tt, partition_num: 2(3), table_num: 1(1), estimate_row: 5(1))

从上面的查询可以看出,查询语句包含了tt关键字,并且扫描的分区个数超过了2,此时执行语句被过滤拦截。需要注意的是,扫描分区的个数并不总是准确的,仅能识别静态的分区剪枝个数,执行过程中的动态剪枝并不能被识别。

使用关键词过滤时可以先使用正则匹配符~*进行测试,正则匹配是忽略大小写的。

另外,由于查询过滤器的规则直接作用在用户block_user上,因此在删除用户block_user时,会提示有依赖项,此时可以通过在语句最后加上cascade进行删除,此时作用在此用户上的查询过滤规则也会被一同删除。

注意,过滤规则命中的依据是,with_parameter命中任意一项,且其他字段的特征也符合即会判定为符合查询过滤规则。

特别注意,不同的计划,可能部分字段无法按照预期进行拦截,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
gaussdb=# CREATE BLOCK RULE test FILTER BY sql('test')WITH(estimate_row='3');
CREATE BLOCK RULE
gaussdb=# SELECT * FROm test;
 c1 | c2
----+----
  1 |  2
  1 |  2
  1 |  2
  1 |  2
  1 |  2
(5 rows)

此时,语句关键字是可以匹配上的,查询的行数也超过了3行的限制,那为什么无法拦截呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
gaussdb=# EXPLAIN VERBOSE SELECT * FROM test;
                                          QUERY PLAN
-----------------------------------------------------------------------------------------------
  id |                  operation                   | E-rows | E-distinct | E-width | E-costs
 ----+----------------------------------------------+--------+------------+---------+---------
   1 | ->  Data Node Scan on "__REMOTE_FQS_QUERY__" |      0 |            |       0 | 0.00

      Targetlist Information (identified by plan id)
 --------------------------------------------------------
   1 --Data Node Scan on "__REMOTE_FQS_QUERY__"
         Output: test.c1, test.c2
         Node/s: All datanodes (node_group, bucket:16384)
         Remote query: SELECT c1, c2 FROM public.test

通过计划可以看出,此时是FQS(fast_query_shipping)计划,导致没有估算信息。因此此时无法进行拦截,对于CN轻量化的计划也是一样的,如果让语句强制走stream计划,那么就可以拦截成功:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
gaussdb=# SET enable_stream_operator=on;
SET
gaussdb=# SET enable_fast_query_shipping=off;
SET
gaussdb=# SELECT * FROM test;
ERROR:  hit block rule test(regexp_sql: test, estimate_row: 3(5))
gaussdb=#  EXPLAIN VERBOSE SELECT * FROM test;
                                       QUERY PLAN
-----------------------------------------------------------------------------------------
  id |               operation                | E-rows | E-distinct | E-width | E-costs
 ----+----------------------------------------+--------+------------+---------+---------
   1 | ->  Row Adapter                        |      5 |            |       8 | 69.00
   2 |    ->  Vector Streaming (type: GATHER) |      5 |            |       8 | 69.00
   3 |       ->  CStore Scan on public.test   |      5 |            |       8 | 59.01

      Targetlist Information (identified by plan id)
 --------------------------------------------------------
   1 --Row Adapter
         Output: c1, c2
   2 --Vector Streaming (type: GATHER)
         Output: c1, c2
         Node/s: All datanodes (node_group, bucket:16384)
   3 --CStore Scan on public.test
         Output: c1, c2
         Distribute Key: c1

所以,如果估算信息不准确,也会导致误拦截或者漏拦截的情况,因为计划的信息是通过估算得到的,因此这种情况无法避免。

使用语句归一化特征值进行查询过滤

语句归一化的特征值,目前有两个,分别是unique_sql_id和sql_hash,两者均是对查询树进行哈希计算之后得出的,区别在于前者是64位哈希值,后者是md5值,因此前者的重复概率会大于后者,在使用时尽量使用sql_hash进行过滤。

获取这两个值的方法:

方法一:查看explain结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
gaussdb=> EXPLAIN VERBOSE SELECT * FROM tt WHERE a>1;
                                              QUERY PLAN
 ----------------------------------------------------------------------------------------------------
   id |                     operation                     | E-rows | E-distinct | E-width | E-costs
  ----+---------------------------------------------------+--------+------------+---------+---------
    1 | ->  Row Adapter                                   |      1 |            |       8 | 16.00
    2 |    ->  Vector Streaming (type: GATHER)            |      1 |            |       8 | 16.00
    3 |       ->  Vector Partition Iterator               |      1 |            |       8 | 6.00
    4 |          ->  Partitioned CStore Scan on public.tt |      1 |            |       8 | 6.00

    Predicate Information (identified by plan id)
  -------------------------------------------------
    3 --Vector Partition Iterator
          Iterations: 3
    4 --Partitioned CStore Scan on public.tt
          Filter: (tt.a > 1)
          Pushdown Predicate Filter: (tt.a > 1)
          Partitions Selected by Static Prune: 1..3

  Targetlist Information (identified by plan id)
  ----------------------------------------------
    1 --Row Adapter
          Output: a, b
    2 --Vector Streaming (type: GATHER)
          Output: a, b
          Node/s: datanode1
    3 --Vector Partition Iterator
          Output: a, b
    4 --Partitioned CStore Scan on public.tt
          Output: a, b

               ====== Query Summary =====
  -----------------------------------------------------
  Parser runtime: 0.029 ms
  Planner runtime: 0.286 ms
  Unique SQL Id: 2229243778
  Unique SQL Hash: sql_aae71adfaa3d91bfe75499d92ad969e8
 (34 rows)

方法二:查看TopSQL记录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
queryid                     | 95701492082350773
 query                       | select * from tt where a>10;
 query_plan                  | 1 | Row Adapter  (cost=14.00..14.00 rows=1 width=8)
                             | 2 |  ->Vector Streaming (type: GATHER)  (cost=0.06..14.00 rows=1 width=8)
                             | 3 |   ->Vector Partition Iterator  (cost=0.00..4.00 rows=1 width=8)
                             |   |     Iterations: 2
                             | 4 |    ->Partitioned CStore Scan on public.tt  (cost=0.00..4.00 rows=1 width=8)
                             |   |      Filter: (tt.a > 10)
                             |   |      Pushdown Predicate Filter: (tt.a > 10)
                             |   |      Partitions Selected by Static Prune: 2..3
 node_group                  | installation
 pid                         | 139803379566936
 lane                        | fast
 unique_sql_id               | 2229243778
 session_id                  | 1732413324.139803379566936.coordinator1
 min_read_bytes              | 0
 max_read_bytes              | 0
 average_read_bytes          | 0
 min_write_bytes             | 0
 max_write_bytes             | 0
 average_write_bytes         | 0
 recv_pkg                    | 2
 send_pkg                    | 2
 recv_bytes                  | 3297
 send_bytes                  | 57
 stmt_type                   | SELECT
 except_info                 |
 unique_plan_id              | 0
 sql_hash                    | sql_aae71adfaa3d91bfe75499d92ad969e8

可以看出两种方法都可以轻松获取这两个语句归一化的特征值,explain可以在事前提前获取,topsql可以在语句执行后进行获取。

注意,语句中的条件有变化,也不会影响归一化的特征值,因为归一化过程中会去除常量的影响,上述的举例中两个语句条件中的常量值并不相同,但归一化的特征值确实一样的。

异常熔断示例

确保CCN运行正常、功能正常。 用户配置自定义异常规则或触发默认异常规则。 作业异常熔断阈值query_exception_count_limit大于等于0

  1. 设置异常熔断阈值。假设设置query_exception_count_limit=1,即只要作业触发异常规则作业就会被加入黑名单。

  2. 配置异常规则。

    创建CPU平均使用率异常规则cpu_percent_except,作业运行时间超过2000秒且CPU使用率达到30%时触发异常退出。

    1
    CREATE EXCEPT RULE cpu_percent_except WITH(ELAPSEDTIME=2000, CPUAVGPERCENT=30);
    

    异常规则还支持BLOCKTIME、ALLCPUTIME、SPILLSIZE等异常的识别处理。

  3. 创建资源池respool1关联异常规则cpu_percent_except。

    1
    CREATE RESOURCE POOL respool1 WITH(except_rule='cpu_percent_except');
    

    资源池支持最多关联63个异常规则集,每个异常规则集间独立生效,互不影响。

  4. 创建业务用户usr1。

    1
    CREATE USER usr1 RESOURCE POOL 'respool1' PASSWORD 'XXXXXX';
    

  5. 用户usr1运行作业触发异常规则后作业被记录到黑名单。

    创建的用户usr1运行作业,作业运行时间超过2000秒且CPU使用率达到30%时触发“cpu_percent_except”异常规则,作业触发异常规则后系统对作业进行以下处理: 将作业异常信息保存至系统表GS_BLOCKLIST_QUERY中。 如果作业触发异常熔断,将系统表GS_BLOCKLIST_QUERY中作业黑名单标志置为true。 更新GS_BLOCKLIST_QUERY中作业黑名单信息。

  6. 查询作业黑名单和异常信息。

    1
    2
    3
    4
    5
    SELECT * FROM dbms_om.gs_blocklist_query;
     unique_sql_id | block_list | except_num |        except_time
    ---------------+------------+------------+----------------------------
        4066836196 | t          |          1 | 2022-08-08 18:00:00.596269
    (1 row)
    

  7. 用户usr1再次运行作业触发异常熔断,DWS的异常熔断机制禁止该作业执行。

    1
    2
    ERROR:  The query is in the blocklist and cannot be run, unique_sql_id(4066836196).
    HINT:  If you want to run the query later, confirm the reason why the query is blocklisted and remove the query from the blocklist after resolving the problem.
    

  8. 优化用户usr1所运行的SQL后,将ID为4066836196的SQL从黑名单移除。

    确认SQL异常原因,如果异常规则配置不合理,修改异常规则;如果异常规则合理,对SQL进行优化后重新运行。确认问题解决后将SQL移除黑名单。

    1
    2
    3
    4
    5
    SELECT gs_remove_blocklist(4066836196);
     gs_remove_blocklist
    ---------------------
     t
    (1 row)
    

查看过滤时间和拦截记录

可以配置GUC参数analysis_options查看查询过滤规则对正常语句所消耗的时间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
SET analysis_options='on(BLOCK_RULE)';

-- explain performance + query

                    User Define Profiling
-----------------------------------------------------------------
Segment Id: 3  Track name: Datanode build connection
      datanode1 (time=0.288 total_calls=1 loops=1)
      datanode2 (time=0.301 total_calls=1 loops=1)
      datanode3 (time=0.321 total_calls=1 loops=1)
      datanode4 (time=0.268 total_calls=1 loops=1)
Segment Id: 3  Track name: Datanode wait connection
      datanode1 (time=0.016 total_calls=1 loops=1)
      datanode2 (time=0.038 total_calls=1 loops=1)
      datanode3 (time=0.021 total_calls=1 loops=1)
      datanode4 (time=0.017 total_calls=1 loops=1)
Segment Id: 1  Track name: block rule check time
      coordinator1 (time=0.028 total_calls=1 loops=1)

创建查询过滤规则后会拦截很多烂SQL,如何看拦截的语句有哪些呢?可以通过TopSQL进行查看,abort_info会记录拦截信息,也就是查询的报错信息。

1
2
3
4
5
gaussdb=# SELECT abort_info,query from GS_WLM_SESSION_INFO WHERE abort_info LIKE '%hit block rule test%';
                        abort_info                         |        query
-----------------------------------------------------------+---------------------
 hit block rule test(regexp_sql: test, estimate_row: 3(5)) | select * from test;
(1 rows)

并发管控

创建查询过滤规则时通过设置max_active_num,可以实现对命中过滤规则的语句进行并发控制。需要注意,max_active_num的最大可选值为1000。

gaussdb=# CREATE BLOCK RULE bl1 filter by sql('t1') WITH (max_active_num='2');
CREATE BLOCK RULE
-- \parallel 模拟并发场景
gaussdb=# \parallel on
Parallel is on with scale default 1024.
gaussdb=# select * from t1;
gaussdb=# select * from t1;
gaussdb=# select * from t1;
gaussdb=# select * from t1;
gaussdb=# \parallel
Parallel is off.
ERROR:  hit block rule bl1(regexp_sql: t1, max_active_num: 2)
ERROR:  hit block rule bl1(regexp_sql: t1, max_active_num: 2)
 a
---
(0 rows)
 a
---
(0 rows)

可以看到,创建了一个并发限制2的查询过滤规则,超过最大并发数的语句就会被拦截。需要注意的是,并发管控功能针对子语句场景,只管控主语句,不限制子语句的并发。

相关文档