文档

SQL限流

更新时间:

PolarDB PostgreSQL版(兼容Oracle)提供了SQL限流功能。SQL限流功能以连接地址为维度配置限流规则,避免异常流量的SQL语句造成业务影响。本文主要为您介绍SQL限流功能的使用方式。

简介

SQL限流功能允许以连接地址为维度配置限流规则,通过SQL模板的方式匹配当前连接地址上执行的SQL并限制其最大并发数或QPS。可被用于以下场景:

  • PolarDB集群上存在慢SQL导致数据库负载较高,影响正常业务执行。

  • 希望对某一类风险SQL限制其可使用的资源,或完全拒绝其执行。

操作步骤

说明

如您需要开启SQL限流功能,请联系我们处理。

  1. 登录PolarDB控制台

  2. 在左侧导航栏单击集群列表

  3. 在左上角,选择集群所在地域。

  4. 找到目标集群,单击集群ID。

  5. 在左侧导航栏单击配置与管理 > 安全管理

  6. SQL限流页签,点击新增,新建SQL限流规则。

    image

  7. 新建SQL限流规则弹窗内设置如下配置项后,单击确定。

    类别

    参数

    说明

    基本信息

    规则名称

    限流规则的名称,需满足如下要求:

    • 不能超过30个字符。

    • 必须由大小写字母和数字组成。

    规则描述

    可选,用于备注该限流规则的相关信息,便于后续管理。不能超过64个字符。

    EndpointID

    选择限流规则作用的连接地址。

    说明
    • 当前仅支持在集群地址和按活跃请求数负载均衡的自定义地址(读写或只读均可)上配置限流规则,主地址以及按连接数负载均衡的只读地址暂时不支持SQL限流。

    • 不同地址上配置的限流规则互不影响,同一地址上配置的限流规则仅对使用该地址的业务连接生效。

    规则配置

    规则类型

    选择限流规则模式,支持活跃并发数限流连接级QPS限流

    说明

    按连接级QPS限流为限制单个连接的每秒请求次数,可用于业务侧配置连接池或使用长连接的场景,短连接场景下建议使用活跃并发数限流。

    当前模式

    选择SQL模板的匹配方式,支持模板匹配全文匹配,两种匹配方式的区别请参考模板匹配与全文匹配

    数据库账号名

    配置限流规则作用的账号,支持配置多个账号,但不超过10个,账号名称之间使用英文逗号分隔。为空时,默认对所有账号生效。

    数据库名

    配置限流规则作用的数据库。支持配置多个数据库,但不超过10个,数据库名称之间使用英文逗号分隔。为空时,默认对所有数据库生效。

    SQL模板

    配置SQL模板,详细配置请参考SQL模板与匹配模式

    最长等待队列长度

    配置最长等待队列长度,取值范围为0-1024。当命中的SQL并发度或QPS达到规则限制时将会进入延迟队列进行重试,当延迟队列中等待的SQL数量超过最长等待队列长度时新的请求将会直接返回错误。合理配置最长等待队列长度可避免大量SQL被限流时延迟队列无限制增长导致数据库代理内存耗尽(OOM)。

    最大活跃并发数

    设置最大活跃并发数。

    说明

    仅选择活跃并发数限流规则类型时,需要配置该参数。

    每连接最大QPS

    设置每连接最大QPS。

    说明

    仅选择连接级QPS限流规则类型时,需要配置该参数。

实现方式

SQL限流为数据库代理侧实现的功能,通过在数据库代理上配置各种限流规则以控制转发的特定SQL的并发数或QPS,对数据库集群的读写或只读节点不会带来额外影响,因此仅支持以集群或自定义地址为维度进行配置。

SQL模板与匹配模式

模板匹配与全文匹配

SQL模板可以是任何符合PolarDB PostgreSQL版(兼容Oracle)集群标准语法的SQL,当配置SQL模板后根据配置的匹配模式不同,数据库代理会对SQL模板进行不同的预处理。

  • 假设配置了一条限流规则并配置了以下SQL模板:

    SELECT * FROM tbl WHERE id < 1;
    • 当选择模板匹配时,会对SQL模板进行简单正则化,去除多余空格、注释,并扫描SQL中的常量部分(单引号字符串、数字等)替换为通配符,得到如下结果:

      -- 模板化结果
      SELECT * FROM tbl WHERE id < ?
    • 当选择全文匹配时,也会对SQL模板进行简单正则化,但不会替换其中的常量部分,结果如下所示:

      -- 仅格式化结果
      SELECT * FROM tbl WHERE id < 1

    之后数据库代理会对生成的格式化SQL生成唯一标识用于后续匹配。

  • 当限流规则启用后,对于每条经过的业务SQL,数据库代理也会进行和SQL模板类似的预处理。以以下业务SQL为例:

    SELECT * FROM tbl WHERE id < 100;

    同样会生成两种格式化的SQL并计算唯一标识,并尝试与限流规则进行匹配:

    -- 模板化结果
    SELECT * FROM tbl WHERE id < ?
    
    -- 仅格式化结果
    SELECT * FROM tbl WHERE id < 100

启用SQL限流规则后在转发当前SQL前,数据库代理会遍历当前配置的限流规则,当限流规则配置为模板匹配时会使用模板化结果与限流规则配置的SQL模板进行匹配,当限流规则配置为全文匹配时会使用仅格式化结果与限流规则配置的SQL模板匹配,匹配命中后会统计其并发度或QPS并执行对应的限流操作。

因此,对于上述的业务SQL和SQL模板,仅当配置为模板匹配时才会命中规则。

参数化支持

SQL模板中允许使用参数化写法,与正常PostgreSQL参数绑定语法一致:

SELECT * FROM tbl WHERE id < $1 AND name = $2 LIMIT 1;

其中,参数化部分在模板匹配和全文匹配中均会被格式化为通配符:

-- 模板化结果
SELECT * FROM tbl WHERE id < $1 AND name = $2 LIMIT ?

-- 仅格式化结果
SELECT * FROM tbl WHERE id < $1 AND name = $2 LIMIT 1

因此对于以下业务SQL:

SELECT * FROM tbl WHERE id < $1 AND name = 2 LIMIT 100;

当前模式配置为模板匹配时可以命中上述规则,当前模式为全文匹配时无法命中上述规则。

Prepared Statment

当业务使用Prepared statement时,Prepare语句本身不会触发限流,只有Execute语句会触发限流。对于Execute语句会对其对应的Prepare语句中的SQL部分进行对应的格式化或模板化并匹配规则。

说明

关于Prepared statement的详细内容请参考PREPARE

示例

使用以下SQL模板配置一条限流规则, 匹配模式选择模板匹配:

SELECT * FROM tbl WHERE id < $1 AND name > $2;

对于以下业务SQL:

-- prepare语句不会触发限流
PREPARE s1 AS SELECT * FROM tbl WHERE id < $1 AND name > 100;

-- execute语句会用其对应的prepare语句中的sql部分匹配限流规则
EXECUTE s1;

EXECUTE s1;

EXECUTE s1;

三条Execute将会命中限流规则并被限流。

同样地,当在限流规则的SQL模板中使用Prepare语句时,也仅会对Prepare语句中的SQL部分进行必要的格式化或模板化用于后续限流,因此创建规则时使用以下两条SQL模板完全等价:

-- 模板1
PREPARE s1 AS SELECT * FROM tbl WHERE id < $1 AND name > $2;

-- 模板2
SELECT * FROM tbl WHERE id < $1 AND name > $2;

扩展协议支持

与Prepare语句类似,当业务驱动使用扩展协议时,仅有Execute报文会触发限流。对每个Execute报文会找到其对应的Parse报文并计算SQL模板匹配限流规则。因此SQL限流可以支持扩展协议,配置时通常无需额外关注业务使用的协议。

说明

关于扩展协议的详细内容请参考社区文档

使用限制

目前SQL限流功能存在以下受限的使用场景:

  • 多语句(Multi-Statement)暂不支持限流,使用多语句时将不会触发任何配置的限流规则。

    多语句指在一条SQL文本中包含多个使用分号间隔的SQL语句,以下是一个使用JDBC驱动执行的多语句示例:

    Statement statement = connection.createStatement();
    
    statement.execute("select 1; select 2; select 3");

    多语句可能同时命中多条限流规则,为避免非预期的结果暂不支持限流。

  • 部分特殊语句,如事务控制语句、存储过程等不支持限流。对如Commit类的事务控制语句限流会导致事务无法正常结束,因此不会命中限流规则。

  • 当客户端或驱动使用批量执行(Statement Batching)模式时,批量执行的SQL仅会触发第一条命中的限流规则。以下是一个使用JDBC驱动的批量执行的示例:

    Statement statement = connection.createStatement();
    
    statement.addBatch("select 1");
    statement.addBatch("select 2");
    statement.addBatch("select 3");
    
    int[] result = statement.executeBatch();
    statement.close();
    connection.close();

    与多语句类似,使用Statement Batching时驱动通常会将多条SQL的扩展协议报文组合并一次性发送,同样会形成类似多语句的同时命中多条限流规则的情况。此时仅会启用命中的第一条限流规则。对于上述的批量执行示例,当连接地址上同时配置以下三条SQL模板的限流规则时:

    -- 模板1
    SELECT 1;
    
    -- 模板2
    SELECT 2;
    
    --模板3
    SELECT 3;

    仅有模板1会命中。

限流方式

目前SQL限流使用SQL模板加延迟队列的技术实现QPS或活跃并发数限流。业务SQL必须命中限流规则后才会统计对应规则上的QPS或并发数。当并发数或QPS超过规则配置的限制时,数据库代理会将该SQL放入延迟队列中延迟一段时间后重试,从而维持数据库侧的并发度/QPS不超过规则限制。

延迟队列的延迟时间与规则配置的QPS/并发数成反比。同时命中某条规则的SQL在延迟队列中等待的最大数量受规则中配置的最长等待队列长度限制,超出限制时代理将不会转发该SQL,同时向客户端返回以下错误:

SELECT 123;
Current query is being throttled and waiting queue is full.
说明

以上报错不会中断或改变当前连接的事务状态,客户端在收到该错误后依然可以选择提交或回滚该事务。

同时,当配置的SQL限流规则中最大活跃并发数或每连接最大QPS为0时,任何命中该规则的SQL都会被拒绝转发并直接向客户端返回上述错误,可以使用该方式完全拒绝一类SQL的执行。

说明
  • 延迟队列内部存在最小的重试时间间隔,当配置的最大QPS较大时,实际QPS会略小于设置的最大值。

  • 配置任意SQL限流规则后,无论是否命中,代理都需要对每条业务SQL进行模板化、生成唯一标识并尝试匹配规则等一系列操作,因此启用SQL限流会带来5%-10%的转发性能下降,建议仅在数据库侧存在明显慢SQL影响正常业务运行时进行限流。慢SQL消除后可以直接在控制台禁用限流规则,已配置的规则禁用后不会生效并可以持久化保留,后续可随时启用。

最佳实践

测试配置的限流规则是否生效

由于限流规则可以配置对哪些账号和数据库生效,通常可以新建一个账号并对该账号配置一条并发数或QPS为0的限流规则,以测试是否可以命中期望限流的SQL。

假设希望对以下SQL限流:

SELECT * FROM generate_series(1, 100000);
  1. 创建测试数据库账户,详细步骤请参考创建账号

    image

  2. 对测试账户配置限流规则,并设置活跃最大并发数为0,配置限流规则请参考操作步骤

    image

  3. 验证规则是否生效。当前规则仅对新建测试账户生效,不会对当前业务产生影响。配置后通过规则中选择的连接地址连接到数据库执行该SQL,正常返回错误即可确认生效:

    SELECT * FROM generate_series(1, 100000);
    Current query is being throttled and waiting queue is full.

利用SQL限流处理生产环境慢SQL

  1. 准备测试环境。

  2. 在ECS实例上使用pgbench工具连接PolarDB集群地址并初始化压测数据。

    pgbench -h <PolarDB集群地址> -p <PolarDB集群地址的端口> -i -s 10 -U <PolarDB数据库用户名> <测试数据库名称>

    然后启动压测,使用pgbench内置的tpcb-like模式,模拟正常业务负载。

    pgbench -h <PolarDB集群地址> -p <PolarDB集群地址的端口> -P 1 -b tpcb-like -j 5 -c 10 -M prepared -T 6000 -U <PolarDB数据库用户名> <测试数据库名称> 
  3. 模拟慢SQL场景,新建一个连接会话,在测试数据库中执行如下语句:

    WITH t AS (SELECT md5(i::text) AS id FROM generate_series(1, 10000000) i) SELECT * FROM t ORDER BY id LIMIT 1;

    该SQL会消耗大量计算资源,通常需要5 s左右返回如下结果:

                    id                
    ----------------------------------
     0000023f507999464aa2b78875b7e5d6
    (1 row)

    再次启动pgbench,使用自定义脚本压测上述SQL,启动10个连接,模拟慢SQL导致集群负载高的场景:

    echo "WITH t AS (SELECT md5(i::text) AS id FROM generate_series(1, 10000000) i) SELECT * FROM t ORDER BY id LIMIT 1;" > slow.sql
    
    pgbench -h <PolarDB集群地址> -p <PolarDB集群地址的端口> -P 1 -f slow.sql -j 5 -c 10 -M prepared -T 6000 -U <PolarDB数据库用户名> <测试数据库名称> 

    开始压测后,原本的正常业务负载立刻大幅下跌:

    image

  4. 在控制台使用以下SQL模板配置一条限流规则,规则类型选择活跃并发数限流:

    WITH t AS (SELECT md5(i::text) AS id FROM generate_series($1, $2) i) SELECT * FROM t ORDER BY id LIMIT $3;

    image

    将慢SQL限制并发数为1,启用规则后即可看到业务负载回升,限流规则已生效。

    image

  5. 在控制台修改限流规则,单击目标限流规则操作列的修改将活跃最大并发数进一步下调为0,完全拒绝慢SQL执行:

    image

    配置后即可看到慢SQL压测端返回错误并中断,至此业务负载完全恢复。

    image