×
技术社区 >  技术博客 >  OceanBase SQL限流实战:告别数据库雪崩,关键字限流才是终极解法

OceanBase SQL限流实战:告别数据库雪崩,关键字限流才是终极解法

数据库突发高负载、慢SQL抢占资源、业务接口雪崩?
传统限流治标不治本,关键字限流前置拦截,从根源守住数据库稳定性!尤其是OceanBase这类分布式数据库,多租户、高并发场景下,SQL限流更是运维必备技能。今天就深度拆解OceanBase两种限流方案,重点揭秘关键字限流的实战用法。

SQL限流的概念和使用场景

SQL限流是一种数据库流量控制机制,用于对特定的SQL请求进行并发或资源使用上的限制,以防止异常或高负载的SQL语句影响整个数据库系统的稳定性与性能。

当某些SQL请求过于频繁或消耗过多资源(如CPU、IO、响应时间等)时,可能会影响其他正常业务的执行。通过设置SQL限流规则,可以:

  • 提前终止不符合要求的SQL请求;
  • 限制并发执行数量;
  • 基于优先级控制影响范围;
  • 避免个别SQL占用过多系统资源,从而保障整体服务的可用性和响应速度。

使用场景

  1. 控制高并发访问:限制某一类SQL的最大并发执行数,避免数据库过载。
  2. 应急响应手段:在出现突发流量或SQL性能问题时,作为快速止损措施。

SQL限流的常用方法

1. 设置 SQL 特征限流(Throttle)

对特定优先级SQL限流

ALTER SYSTEM ENABLE SQL THROTTLE FOR PRIORITY <= 100 USING QUEUE_TIME=0.1; 
--对 PRIORITY 小于等于 100 的 Session 上队列等待时间超过 0.1s 的请求限流。

RT:按照sql 响应时间限流

QUEUE_TIME:按照队列等待时间限流

具体限流特征支持情况需要看对应版本的文档说明

参考文档:https://www.oceanbase.com/docs/enterprise-oceanbase-database-cn-10000000000946702

2. 绑定Hint限流(创建Outline)

使用 Outline 绑定, 添加/*+max_concurrent(N)*/这个 Hint。其中,N 表示某类 Query 可同时执行的请求数,实际内部限制的是某个 Plan 可同时执行的请求数;

支持使用文本限流、SQL_ID 限流以及模糊限流三种方式来绑定 Outline。

sql_text限流

CREATE [OR REPLACE] OUTLINE outline_name ON stmt_with_hint [ TO  ];

SQL_ID 限流

CREATE OUTLINE outline_name ON sql_id USING HINT hint_text;

format outline

CREATE [OR REPLACE] FORMAT OUTLINE outline_name ON stmt_with_hint [ TO  ];
CREATE FORMAT OUTLINE outline_name ON sql_id USING HINT hint_text;
  1. format outline使用需要注意版本是否支持。
  2. 当同一条 SQL 可以匹配多个限流规则时,会选择并发度最小的进行限流。
  3. 所有的 Outline 相关功能都只能针对本租户,不能通过系统租户对其他租户进行 Outline 相关操作。

SQL限流存在的问题

查看DBA_OB_OUTLINES视图:发现通过ocp为该SQL_ID创建了outline_content为/*+max_concurrent(1) */的outline!

查看该SQL_ID(1FA56D57B98FCA5F4151B385D0C4F736)的执行计划:发现outline已经被用到执行计划里了

select PLAN_ID,OUTLINE_ID,OUTLINE_DATA from oceanbase.gv$ob_plan_cache_plan_stat where sql_id = '1FA56D57B98FCA5F4151B385D0C4F736';
+---------+------------+-----------------------------------------------------------------------------------------------------------------------+
| PLAN_ID | OUTLINE_ID | OUTLINE_DATA                                                                                                          |
+---------+------------+-----------------------------------------------------------------------------------------------------------------------+
|  234967 |     583343 | /*+BEGIN_OUTLINE_DATA FULL(@"SEL$1" "cjx_test"."t1"@"SEL$1") OPTIMIZER_FEATURES_ENABLE('4.2.1.11') END_OUTLINE_DATA*/ |
|  317638 |     583343 | /*+BEGIN_OUTLINE_DATA FULL(@"SEL$1" "cjx_test"."t1"@"SEL$1") OPTIMIZER_FEATURES_ENABLE('4.2.1.11') END_OUTLINE_DATA*/ |
+---------+------------+-----------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.06 sec)

查看sql_audit:发现后续该sql_id的sql执行都使用了带有outline的执行计划

总结:白屏是通过为sql_id创建 /* max_concurrency(N) */的outline来实现并发限流。

当前限流实现逻辑

当前创建完outline限流规则后,若想限流规则生效,仍需进入SQL引擎。

使用outline的限流逻辑必须要先通过Parse、Resolve和Optimize阶段生成出执行计划,且在将计划添加进执行计划缓存。后续SQL进入进行快速参数化访问plan_cache后才会检查是否触发限流上限。对于一些在Parse、Resolve阶段就卡死的SQL来说,此类限流方法将失效。需要一种新的限流方式预期能够在SQL请求进入SQL引擎之前就将其拦住。

为此,引入了关键字限流

关键字限流

对于 V4.3.X 版本,该语句从 OceanBase 数据库 V4.3.5 BP3 版本开始引入。
对于 V4.4.X 版本,该语句从 OceanBase 数据库 V4.4.1 版本开始引入。

CREATE CONCURRENT_LIMITING_RULE [ IF NOT EXISTS ] `ccl_rule_name`
ON `database`.`table`
TO ''@'ip'
FOR { ALL | UPDATE | SELECT | INSERT | DELETE }
filter_options:
    [ FILTER BY KEYWORD('KEYWORD1', 'KEYWORD2',…) ]
 with_options:
    WITH MAX_CONCURRENCY = value1 [per sql]

核心参数解读

  1. 限流规则名称不能超过 128 字节 。
  2. 限流规则个数目前无限制 。
  3. DBname 和 tablename在MySQL模式下会受 lower_case_table_names 变量的影响。
  4. 关键字之间将用通配符%连接,例如给定3个关键词 'abc' , 'c1 = 2' , 'xyz' 后续将按照 '%abc%c1=2%xyz%' 这个字符串进行SQL 文本的通配匹配。
  5. 当关键字中有'_'时会被转义,如关键字'table_t1'会被记录成'%table_t1%',限流关键字无个数限制,但限制拼凑后的字符串长度不能超过OB_MAX_VARCHAR_LENGTH=1048576 。
  6. 关键字字符串的字符集和字符序为binary
    其中 MAX_CONCURRENCY=10指单机并发上限,即其为机器级而非集群级的。比如假设集群有3 台机器,那么指的是这三台机器各自的限流并发上限为 10。
  7. per sql为可选的选项。如果没有 per sql,表示统计粒度为规则级别,那么sql1和sq12同一时间全部 SQL 数量总计不能超过 10;如果有 per sql,表示统计粒度为 Format SQL_ID 语句级别,sq1和sql2 将各自被限制执行并发上限,同一时间每个 SQL 数量总计不能超过 10。

eg:不加per sql

统计粒度:所有命中该规则的 SQL 总并发≤MAX_CONCURRENCY

 CREATE CONCURRENT_LIMITING_RULE IF NOT EXISTS product_reviews
    -> ON *.*
    -> TO '%'@'%'
    -> FOR ALL
    -> FILTER BY KEYWORD('product_reviews')
    -> WITH MAX_CONCURRENCY = 1;

在两个窗口模拟并发,执行如下语句,预期会命中限流规则报错

 select rating,sleep(1) from product_reviews;

同时执行两个语句

执行结果:命中限流规则

加per sql

统计粒度:每个格式化后的 SQL 单独计数,各自并发≤1

创建新的限流规则(多了一个PER SQL)

MySQL [test]>  CREATE CONCURRENT_LIMITING_RULE IF NOT EXISTS product_reviews
    -> ON *.*
    -> TO '%'@'%'
    -> FOR SELECT
    -> FILTER BY KEYWORD('product_reviews')
    ->  WITH MAX_CONCURRENCY = 1 per sql;
Query OK, 0 rows affected (0.17 sec)

在两个窗口同时分别执行不一样SQL_ID的查询语句

-- 窗口1 SQL
select user_id,rating,sleep(1) from product_reviews
-- 窗口1 SQL_ID: 02E17F50C77BA44C23A4F3B7B031F004

-- 窗口2 SQL
 select rating,product_id,sleep(1) from product_reviews
-- 窗口2 SQL_ID: 1FF7D06783EC120A29EE890C85ED15D8

执行结果如下:

执行成功,未命中CCL_RULE

添加一个窗口3,执行和窗口1一样的语句,预期就会命中限流规则报错

从sql_audit也可以查看CCL_RULE_ID来判断是否命中CCL_RULE

                         SVR_IP: XX.XX.XX.XX
                       SVR_PORT: 2882
                     REQUEST_ID: 1635878
                    SQL_EXEC_ID: 20545345
                       TRACE_ID: YB42C0A8054B-000642EF0AE65536-0-0
                            SID: 297797
                      CLIENT_IP: XX.XX.XX.XX
                    CLIENT_PORT: 46744
                      TENANT_ID: 1004
                    TENANT_NAME: test
            EFFECTIVE_TENANT_ID: 1004
                        USER_ID: 200001
                      USER_NAME: root
                     USER_GROUP: 0
                 USER_CLIENT_IP: XX.XX.XX.XX
                          DB_ID: 500001
                        DB_NAME: test
                         SQL_ID: 02E17F50C77BA44C23A4F3B7B031F004
                      QUERY_SQL: select user_id,rating,sleep(1) from product_reviews
                        PLAN_ID: 0
                  AFFECTED_ROWS: 0
                    RETURN_ROWS: 0
                  PARTITION_CNT: 1
                       RET_CODE: -11087
                          QC_ID: 0
                         DFO_ID: 0
                         SQC_ID: 0
                      WORKER_ID: 0
                          EVENT:
                         P1TEXT:
                             P1: 0
                         P2TEXT:
                             P2: 0
                         P3TEXT:
                             P3: 0
                          LEVEL: 0
                  WAIT_CLASS_ID: 100
                    WAIT_CLASS#: 0
                     WAIT_CLASS: OTHER
                          STATE: MAX_WAIT TIME ZERO
                WAIT_TIME_MICRO: 0
          TOTAL_WAIT_TIME_MICRO: 0
                    TOTAL_WAITS: 0
                      RPC_COUNT: 0
                      PLAN_TYPE: 0
                   IS_INNER_SQL: 0
                IS_EXECUTOR_RPC: 0
                    IS_HIT_PLAN: 0
                   REQUEST_TIME: 1762600230468937
                   ELAPSED_TIME: 819
                       NET_TIME: 0
                  NET_WAIT_TIME: 3
                     QUEUE_TIME: 240
                    DECODE_TIME: 1
                  GET_PLAN_TIME: 440
                   EXECUTE_TIME: 79
          APPLICATION_WAIT_TIME: 0
          CONCURRENCY_WAIT_TIME: 0
              USER_IO_WAIT_TIME: 0
                  SCHEDULE_TIME: 0
                  ROW_CACHE_HIT: 0
         BLOOM_FILTER_CACHE_HIT: 0
                BLOCK_CACHE_HIT: 0
                     DISK_READS: 0
                      RETRY_CNT: 0
                     TABLE_SCAN: 0
              CONSISTENCY_LEVEL: -1
        MEMSTORE_READ_ROW_COUNT: 0
         SSSTORE_READ_ROW_COUNT: 0
            DATA_BLOCK_READ_CNT: 0
           DATA_BLOCK_CACHE_HIT: 0
           INDEX_BLOCK_READ_CNT: 0
          INDEX_BLOCK_CACHE_HIT: 0
            BLOCKSCAN_BLOCK_CNT: 0
              BLOCKSCAN_ROW_CNT: 0
PUSHDOWN_STORAGE_FILTER_ROW_CNT: 0
            REQUEST_MEMORY_USED: 8565248
          EXPECTED_WORKER_COUNT: 0
              USED_WORKER_COUNT: 0
                     SCHED_INFO: NULL
             FUSE_ROW_CACHE_HIT: 0
              PS_CLIENT_STMT_ID: -1
               PS_INNER_STMT_ID: -1
                          TX_ID: 0
               SNAPSHOT_VERSION: 0
                   REQUEST_TYPE: 2
          IS_BATCHED_MULTI_STMT: 0
                  OB_TRACE_INFO: NULL
                      PLAN_HASH: 0
             LOCK_FOR_READ_TIME: 0
                   PARAMS_VALUE:
                      RULE_NAME:
                  PARTITION_HIT: 1
            TX_INTERNAL_ROUTING: 0
               TX_STATE_VERSION: 0
                   FLT_TRACE_ID: 00064313-589e-3d45-641f-4857b908d45e
                    PL_TRACE_ID: NULL
                PLSQL_EXEC_TIME: 0
                  FORMAT_SQL_ID: 2E1AEEDF0CE5A757B4D4BEAF7D16AE89
                      STMT_TYPE: SELECT
  TOTAL_MEMSTORE_READ_ROW_COUNT: 0
   TOTAL_SSSTORE_READ_ROW_COUNT: 0
                     PROXY_USER:
                        SEQ_NUM: 0
              NETWORK_WAIT_TIME: 0
             PLSQL_COMPILE_TIME: 0
     INSERT_DUPLICATE_ROW_COUNT: 0
                    CCL_RULE_ID: 500016
                 CCL_MATCH_TIME: 148   

参考视图

oceanbase.DBA_OB_CCL_RULES
GV$OB_SQL_CCL_STATUS

AFFECT_DML值和对应的SQL类型

0 ALL

1 SELECT

2 UPDATE

3 INSERT

4 DELETE

SQL限流是数据库运维的最后一道防线,OceanBase关键字限流彻底解决了传统限流的滞后性问题,实现前置拦截、精准控流、低耗高效

你在运维实战中,曾遭遇哪些棘手难题?欢迎在评论区交流!

精选推荐