【运维技巧】OceanBase 中的非机动车道 —— SQL 限流技巧分享

定场诗《回家的路》

从公司回家时,

每次都会路过丽水路。

这条路上植被很多,

旁边就是京杭大运河。

可惜路又长又窄,

长四公里,

窄到机动车道和非机动车道被合二为一。

回家路上经常看到这样的景象:

几辆自行车骑上了这条窄窄的路,

后面的汽车怕撞到自行车,

只能一路陪着骑车的人慢悠悠地开。

背景

大家先思考这样一个问题:在一条机动车道和非机动车道被合二为一的道路上,单位时间内,大家会选择让十辆汽车通过,还是选择让一辆自行车和一辆汽车通过?

可能大部分人都会认为,让更多汽车快速通过,才能让这条路发挥出更大的作用。

类似地,在 AP、TP 业务混布的场景下,因为 CPU 资源有限,OceanBase 认为,让 CPU 尽量先服务更大量的 TP 小请求,会更有意义[1]。

曾经在《OceanBase 中的身外身法 —— Auto DOP(自适应并行)使用姿势分享》中,提到过一个 “大查询队列” 的概念。今天,就来给大家简单介绍下 OceanBase 为 AP 大查询开辟的这个非机动车道 —— 大查询队列,以及 OceanBase 中 SQL 限流的常用方法。

大查询的判定时机

如果要限制大查询的资源占用,首先,就需要判定一条 SQL 是否属于大查询。

OceanBase 4.x 版本中有一个集群级的配置项叫做 large_query_threshold,默认值是 5 秒。

第一次执行 Query 时,因为无法提前预估 Query 的执行时间,所以默认不是大查询。

如果不是第一次执行这条 Query,因为 Plan Cache 中已经缓存了这条 Query 的计划,所以会根据 Plan Cache 中缓存的之前这条 Query 的平均执行时间,来判定是否属于大查询。

大查询的限制策略

如果 Query 被判定为大查询,则会被直接丢到大查询队列重试,让出当前工作线程。

大查询有单独的线程组调度执行,该线程组与普通工作线程组对等。相当于数据库系统自动会为大查询开辟了一条 “非机动车道”。

大查询队列中的 Query,会受配置项 large_query_worker_percentage(默认值 30%)控制。效果类似于为大查询被单独开了一个资源组,这个资源组内被限制只能使用 30% 的 CPU 资源进行计算。

限流技巧分享

如果突然出现多个第一次执行的大查询,普通工作线程都被占用了怎么办?

缓解方式是:kill 数据库的 session,重连即可恢复。

因为 kill session 之后,Plan Cache 依然会缓存没执行完的 Query 的计划和记录曾经的执行时间。重连 session 之后,大查询就会在编译期通过大查询预判,进入大查询队列,不再占用普通工作线程。

PX 线程怎么限制资源占用?

PX 线程(并行执行工作线程)有独立的线程组,不会受到大查询的限制。大查询队列只会影响普通工作线程组。

这里有一点需要说明:当没有小查询的时候,大查询可以占用全部普通工作线程组的资源。只有当同时有大查询和小查询时,默认 30% 的比例才生效。

因为并行执行本就不应该通过大查询队列来进行限制:一边要用更多资源让 Query 并行执行,跑的更快,一边又要限制不能用更多资源。PX 线程如果要进行资源限制和隔离,还是需要用到上周提到的资源组(resource group)[2]。

SQL 限流功能

最后顺带介绍 OceanBase 中几个和 SQL 限流相关的功能。

使用文本限流

如果要限制某类 SQL 请求执行流量,可以通过创建 outline,添加 /*+max_concurrent(N)*/ 这个 hint,N 表示某类 Query 同时可执行的请求数(实际内部限制的是某类 Plan 同时执行的请求个数)。

语法:

CREATE [OR REPLACE]
  OUTLINE outline_name
  ON stmt [TO target_stmt];

举例:

create table t1(c1 int, c2 int);

-- ? 表示通配符,限制这类请求个数最多为 0
create outline ol_1 on
  select /*+max_concurrent(0)*/ *
  from t1
  where c1 = 1 and c2 = ?;

-- 执行失败
select *
  from t1
  where c1 = 1 and c2 = 0;
ERROR 5268 (HY000): SQL reach max concurrent num 0

-- 执行失败
select *
  from t1
  where c1 = 1 and c2 = 1;
ERROR 5268 (HY000): SQL reach max concurrent num 0

-- 执行成功
select *
  from t1
  where c1 = 12345 and c2 = 2;
Empty set (0.01 sec)

模糊限流

语法:

CREATE [OR REPLACE]
  FORMAT OUTLINE outline_name
  ON format_stmt [TO format_target_stmt]

其中 format_stmt 表示归一化后的 SQL,归一化规则如下:

  • 归一化常量参数

  • 归一化为大写

  • 忽略空格、换行符等非语法定义符号差异

  • 对于in表达式,将会做归一化

obclient>
select
  statement_digest_text(
    'select *    from 
    t1 where c1 in (1, 2) and c2 = 3'
  );
result : SELECT * FROM T1 WHERE C1 IN (...) AND C2 = ?
1 row in set (0.00 sec)

举例:

-- 创建 format -- 创建 format outline
create format outline fmt_otl
on
  select /*+max_concurrent(0)*/ *
  from t1
  where c1 in (?, ?) and c2 = ?;

-- 执行失败
select *
  from         t1
  where c1 in (1)
        and c2 = 2;
ERROR 5268 (HY000): SQL reach max concurrent num 0

-- 执行失败
select *
  from t1
  where c1 in (1, 2)
        and c2 = 3;
ERROR 5268 (HY000): SQL reach max concurrent num 0

-- 执行失败
select *
  from t1
  where c1 in (1, 2, 3)
        and c2 = 4;
ERROR 5268 (HY000): SQL reach max concurrent num 0

参考资料

[1] OceanBase 认为,让 CPU 尽量先服务更大量的 TP 小请求,会更有意义

[2] 资源组(resource group)

15 个赞

本帖内容来源是:公众号 “老纪的技术唠嗑局” 中的《SQL 限流技巧分享》,欢迎大家扫码关注公众号!

我们会在这个公众号上第一时间更新和数据库相关的技术内容!

image

15 个赞

学习了

15 个赞

666,学习了

10 个赞

这个角度研究很贴切啊

7 个赞

学习了

7 个赞

:smile: :grinning: :blush:

5 个赞

讲的太好了,深入浅出

6 个赞

:index_pointing_at_the_viewer: :index_pointing_at_the_viewer: :index_pointing_at_the_viewer: :clap: :clap: :clap:

5 个赞

学习中

5 个赞

:fist: :fist: :fist: :fist: :fist: :fist:

5 个赞

努力学习,总结经验 :clap: :clap: :clap:

5 个赞

:grinning: :grinning: :grinning: :grinning: :grinning:

3 个赞

通俗易懂 :+1:

4 个赞

真的不错

4 个赞

:wave: :wave:

3 个赞

:wave: :wave: :wave:

4 个赞

感谢分享

5 个赞

:checkered_flag: :checkered_flag: :checkered_flag:

4 个赞

高质量

4 个赞