|
| 1 | +--- |
| 2 | +title: 系统响应时间不符合预期 |
| 3 | +weight: 2 |
| 4 | +--- |
| 5 | + |
| 6 | +## 业务及数据库表现 |
| 7 | + |
| 8 | +业务表现:业务响应由快变慢,或者直接超时报错。 |
| 9 | + |
| 10 | +数据库表现:SQL 执行时间变长,响应曲线陡升,SQL 响应延迟 (RT) 明显变高。 |
| 11 | + |
| 12 | +## 排查方向和流程 |
| 13 | + |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | +### 排查硬件问题 |
| 18 | + |
| 19 | +首先需要排除一下是否由网络、磁盘 IO 等硬件问题导致的 SQL 响应延迟,这里不多啰嗦。 |
| 20 | + |
| 21 | +<br></br> |
| 22 | + |
| 23 | +### 排查大查询(slow query)问题 |
| 24 | + |
| 25 | +直接通过 OCP 排查是否存在 “可疑 SQL”。 |
| 26 | + |
| 27 | + |
| 28 | +如果存在某个大查询(或者叫大请求)占用了过多资源,导致大量短请求无法及时得到响应,可以考虑以下方法: |
| 29 | + |
| 30 | +- 通过 OCP 对占用过多资源的大请求进行限流。 |
| 31 | + |
| 32 | + |
| 33 | +- 通过 [large_query_worker_percentage](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001576444)(默认 30%) 来设置大查询可以使用的系统资源占比。 |
| 34 | + |
| 35 | +- 通过 [large_query_threshold](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001576249) (默认 5s)来设置大查询的判定阈值。这个时间阈值修改之后对系统影响可能会比较大,在不了解参数含义时,不建议用户随意修改。 |
| 36 | + |
| 37 | +- 通过 OCP 添加主机,并对租户[进行扩容](https://www.oceanbase.com/docs/common-ocp-1000000001405989)(Zone 内增加节点)。 |
| 38 | + |
| 39 | +<br></br> |
| 40 | + |
| 41 | +### 排查租户队列积压问题 |
| 42 | + |
| 43 | +#### 排查方法 |
| 44 | + |
| 45 | +资源问题这里除了某几个大查询占用了过多资源,还有一种租户队列积压的情况。排查方式有以下几种: |
| 46 | + |
| 47 | +- 可以通过 OCP 观察单条 slow query 的平均 queue_time(排队时间)在 elapsed_time(响应时间) 中的占比是否符合预期,如果 queue_time 超长,则可能是租户队列积压。 |
| 48 | + |
| 49 | + |
| 50 | + |
| 51 | +- 或者可以通过 GV$OB_SQL_AUDIT 视图查询 slow query 各种维度的信息,详见:《DBA 入门教程》中的 [“分析 SQL 监控视图”](https://www.oceanbase.com/docs/community-tutorials-cn-1000000001390074) 小节。一些重要的事件间隔如下: |
| 52 | + |
| 53 | + ``` |
| 54 | + -- 这个例子中查询的是:tenant id 为 1002 的租户,在 2024-11-20 12:00:00 之后 |
| 55 | + -- query 执行时间超过 100 ms 的其中一条 SQL |
| 56 | + |
| 57 | + select |
| 58 | + tenant_id, |
| 59 | + request_id, |
| 60 | + usec_to_time(request_time), |
| 61 | + elapsed_time, |
| 62 | + queue_time, |
| 63 | + execute_time, |
| 64 | + query_sql |
| 65 | + from |
| 66 | + oceanbase.GV$OB_SQL_AUDIT |
| 67 | + where |
| 68 | + tenant_id = 1002 |
| 69 | + and elapsed_time > 100000 |
| 70 | + and request_time > time_to_usec('2024-11-20 12:00:00') |
| 71 | + order by |
| 72 | + elapsed_time desc |
| 73 | + limit 1 \G |
| 74 | + |
| 75 | + *************************** 1. row *************************** |
| 76 | + tenant_id: 1002 |
| 77 | + request_id: 13329994 |
| 78 | + usec_to_time(request_time): 2024-11-20 15:14:58.765564 |
| 79 | + elapsed_time: 153118 |
| 80 | + queue_time: 34 |
| 81 | + execute_time: 139873 |
| 82 | + query_sql: select * from xxxx where xxxx; |
| 83 | + 1 row in set (0.11 sec) |
| 84 | + ``` |
| 85 | + |
| 86 | +- **<font color="red">如果需要观察特定租户的队列积压情况,可以通过日志中的 'dump tenant info' 来实现,这个是最通用的方法</font>**: |
| 87 | + ``` |
| 88 | + grep 'dump tenant info' observer.log* |
| 89 | + ``` |
| 90 | + |
| 91 | +
|
| 92 | +日志中,有几个关键字可参考: |
| 93 | +
|
| 94 | + 1. **<font color="red">req_queue</font>**,表示普通请求队列 |
| 95 | + |
| 96 | + - total_size=n,n 表示优先级队列中总共的排队请求数。 |
| 97 | +
|
| 98 | + - queue[x]=y,y 表示每个优先级子队列中的排队请求数(x 越小队列优先级越高)。 |
| 99 | +
|
| 100 | + 2. **<font color="red">multi_level_queue</font>**,表示嵌套请求队列 |
| 101 | + |
| 102 | + - total_size=n,n 表示优先级队列中总共的排队请求数。 |
| 103 | + |
| 104 | + - queue[x]=y,y 表示每个层级子队列中的排队请求数。 |
| 105 | + |
| 106 | + - queue0: 存放无嵌套的请求,由于无嵌套请求有优先级队列来存放,因此常为空。 |
| 107 | + |
| 108 | + - queue1: 存放1层嵌套的请求(如 sql 触发的 rpc) |
| 109 | + |
| 110 | + - queue2: 存放2层嵌套的请求(如 sql 触发的 rpc 再次触发的 rpc) |
| 111 | + |
| 112 | + - …… |
| 113 | +
|
| 114 | + 3. **<font color="red">group_id = x, queue_size = y</font>** 中的 y 表示分组队列排队请求数。 |
| 115 | + - group 是用来处理特定种类 rpc 请求的,关于每个 group id 对应哪种 rpc 请求。可以参考开源代码 [ob_group_list.h](https://github.com/oceanbase/oceanbase/blob/develop/src/share/resource_manager/ob_group_list.h)。 |
| 116 | +
|
| 117 | +在日志中搜索 dump tenant info,好处是可以看到历史,可以同时看到队列和线程的大致情况,缺点是日志会周期性打印,实时性不强。 |
| 118 | +
|
| 119 | +- 或者通过登录 sys 租户,执行以下 SQL 观察租户队列积压情况: |
| 120 | +``` |
| 121 | +obclient [oceanbase]> select * from oceanbase.__all_virtual_dump_tenant_info where tenant_id = 1002\G |
| 122 | +*************************** 1. row *************************** |
| 123 | + svr_ip: 11.158.31.20 |
| 124 | + svr_port: 22602 |
| 125 | + tenant_id: 1002 |
| 126 | + compat_mode: 0 |
| 127 | + unit_min_cpu: 2 |
| 128 | + unit_max_cpu: 2 |
| 129 | + slice: 0 |
| 130 | + remain_slice: 0 |
| 131 | + token_cnt: 10 |
| 132 | + ass_token_cnt: 10 |
| 133 | + lq_tokens: 0 |
| 134 | + used_lq_tokens: 0 |
| 135 | + stopped: 0 |
| 136 | + idle_us: 0 |
| 137 | + recv_hp_rpc_cnt: 99 |
| 138 | + recv_np_rpc_cnt: 2226 |
| 139 | + recv_lp_rpc_cnt: 0 |
| 140 | + recv_mysql_cnt: 5459 |
| 141 | + recv_task_cnt: 5 |
| 142 | + recv_large_req_cnt: 0 |
| 143 | + recv_large_queries: 3661 |
| 144 | + actives: 10 |
| 145 | + workers: 10 |
| 146 | + lq_waiting_workers: 0 |
| 147 | +req_queue_total_size: 0 |
| 148 | + queue_0: 0 |
| 149 | + queue_1: 0 |
| 150 | + queue_2: 0 |
| 151 | + queue_3: 0 |
| 152 | + queue_4: 0 |
| 153 | + queue_5: 0 |
| 154 | + large_queued: 0 |
| 155 | +1 row in set (0.001 sec) |
| 156 | +``` |
| 157 | +这张虚拟表,可以实时地查看队列信息,好处是实时性强,缺点是不记录历史信息。 |
| 158 | +
|
| 159 | +#### 解决方法 |
| 160 | +
|
| 161 | +这里先多说几句,OceanBase 的线程模型,是一个典型的生产者消费者模型,当请求的生产速度长期大于消费速度时,租户的工作队列就会被打爆,并返回错误码 -4019。 |
| 162 | +
|
| 163 | +即使没有打满,也会表现出请求排队耗时长的情况,如租户中存在大量自增列的场景(详见:《DBA 入门教程》中的 “扩展功能” 的 [“序列” 部分](https://www.oceanbase.com/docs/community-tutorials-cn-1000000001390115))。 |
| 164 | +
|
| 165 | +解决方法如下: |
| 166 | +
|
| 167 | +- **<font color="red">如果租户队列已经爆了,只能通过重启恢复(停压力也不行)。</font>** 建议去[社区论坛](https://ask.oceanbase.com/)发帖,联系在论坛中值班的技术支持同学,让他们协助你留下 obstack 或者 pstack 的堆栈信息,否则很难诊断具体原因。 |
| 168 | +
|
| 169 | +- **<font color="red">一般租户队列爆的问题较少,更常见的是租户队列积压。</font>** |
| 170 | + - 如果出现租户队列积压,请优先检查是否有 order 属性的自增列。 |
| 171 | +
|
| 172 | + - 如果不是有大量自增列集中写入数据,需要联系社区论坛的值班同学通过 obstack 或者 pstack 分析堆栈信息。 |
| 173 | +
|
| 174 | + - 分析的结论如果不是死锁引起的队列积压,可以通过调大特定租户的规格来缓解特殊场景下的队列积压。这里的 “特殊场景” 指有大量写入导致线程等锁开销,或者操作极耗 cpu(可以通过 top 或者tsar 来验证)。 |
| 175 | +
|
| 176 | +<br></br> |
| 177 | +
|
| 178 | +### 排查计划问题 |
| 179 | +
|
| 180 | +如果资源问题这个分支的嫌疑被排除了,大概率就是计划问题了。 |
| 181 | +
|
| 182 | + |
| 183 | +
|
| 184 | +如果 SQL 执行效率是 “由快变慢”,那么可以重点怀疑以下几个常见问题: |
| 185 | +
|
| 186 | +- 第一个是计划缓存(plan cahce)的 bad case,也被人称为 “大小账号问题”。问题描述、排查思路、解决方法都详见:《DBA 入门教程》中的 “常见的 SQL 调优方式” 的 [“计划缓存的 bad case” 部分](https://www.oceanbase.com/docs/community-tutorials-cn-1000000001390071),这里不再赘述。简而言之,“大小账号问题” 分两种: |
| 187 | + - 一种就是《入门教程》中说的,SQL 中只有常量不同时,会共享 plan cache 中的同一个计划,这种场景可能会有问题; |
| 188 | + > 再多说一句,算是偷偷做个预告:OceanBase 后续的版本会在某些合适的场景中支持为 SQL 中的不同常量,缓存不同的计划,以消除现在这个烦人的问题。 |
| 189 | + - 另一种是 SQL 涉及的表对象的统计信息发生了较大变化,但依然使用的是统计信息发生变化前 plan cache 中缓存的计划。 |
| 190 | + > 这个问题也会在后续版本的 plan cache 中得到缓解。 |
| 191 | + |
| 192 | +
|
| 193 | +- 第二个是 [Buffer 表问题](https://www.oceanbase.com/docs/community-tutorials-cn-1000000001390072)。也详见蓝色链接,不再赘述。 |
| 194 | + > 这里也只多说一句,《DBA 入门教程》中只给了大家一个解决问题的思路,就是想办法提高特定表的合并频率。除了手动合并,OceanBase 数据库从 V4.2.0 版本开始支持[自适应合并](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001574524),通过修改 table_mode 为 queue 模式,来缓解该问题。 |
| 195 | +
|
| 196 | +- 第三个是在版本升级后,在低版本中好的计划,在高版本中回退成了差的计划。 |
| 197 | + - 一般来说,升级之后,绝大多数的计划都会变成更优的计划,这种回退只是极其个别的情况。这种情况在《入门教程》中没有为大家介绍,可以直接通过 OCP 观察对应 SQL 的历史趋势及计划变化来判断。 |
| 198 | +  |
| 199 | + - 解决方法详见[《入门教程》中的 “通过 Hint 生成指定计划” 以及 “通过 Outline 进行计划绑定”](https://www.oceanbase.com/docs/community-tutorials-cn-1000000001390068)。 |
| 200 | +
|
| 201 | +
|
| 202 | +- 第四个是硬解析问题,也可以直接通过 OCP 观察对应 SQL 计划生产时间的历史趋势来判断。解决方法详见[《入门教程》中的 “硬解析问题”](https://www.oceanbase.com/docs/community-tutorials-cn-1000000001390072)。 |
| 203 | + |
| 204 | +
|
| 205 | +
|
| 206 | +如果 SQL 执行效率不是由快变慢,而是 “一如既往的慢”,那就去看这本《DBA 进阶教程》中的 [“SQL 性能诊断和调优” 小节](https://oceanbase.github.io/docs/user_manual/operation_and_maintenance/scenario_best_practices/chapter_03_htap/performance_tuning)吧,哈哈~ |
0 commit comments