走进SQL审计视图——《OceanBase诊断系列》之二

1. 前言

在SQL性能诊断上,OceanBase有一个非常实用的功能 ------ SQL审计视图(gvsql_audit)。在OceanBase 4.0.0及更高版本中,该功能是 gvob_sql_audit。它可以使开发和运维人员更方便地排查在OceanBase上运行过的任意一条SQL,无论这些SQL是成功与否,都有详细的运行信息记录。这些信息包括客户端和服务端的IP端口、SQL语句、执行时间、执行节点、执行计划ID、会话ID、执行时间、等待时间、总时间、排队时间、以及相关的块读取信息和执行报错信息等。

|-------------------|--------------------------------------------------------------|
| 查询方式 | 说明 |
| (g)vob_sql_audit | OceanBase 4.0.0.0 及以上版本,gvxx查询该租户所有机器vxxx查询该租户本机器(不保证路由准确) | | (g)vsql_audit | OceanBase 4.0.0.0 以下版本gvxx查询该租户所有机器vxxx查询该租户本机器(不保证路由准确) |

sql_audit是基于虚拟表__all_virtual_sql_audit的视图, 该虚拟表对应的数据存放在一个可配置的内存空间中,能够记录并显示每一次SQL请求的来源、执行状态及统计信息,由于存放这些记录的内存是有限的,因此到达一定内存使用量,会触发淘汰。

  • sql_audit 每隔 1s 会检测后台任务并根据以下标准决定是否淘汰:
    • sql_audit 内存最大可使用上限为 avail_mem_limit = min (OBServer 可使用内存 *10%,sql_audit_memory_limit)。
    • 当 avail_mem_limit 在 [64M, 100M] 范围内时, 内存使用达到 avail_mem_limit-20M 时触发淘汰。
    • 当 avail_mem_limit 在 [100M, 5G] 范围内时, 内存使用达到 availmem_limit*0.8 时触发淘汰。
    • 当 avail_mem_limit 在 [5G, +∞)范围内时, 内存使用达到 availmem_limit-1G 时触发淘汰。
    • 当 sql_audidt 记录数超过 900 万条时,触发淘汰。
  • sql_audit 根据以下标准决定是否停止淘汰:
    • 如果是达到内存上限触发淘汰则:
    • 当 avail_mem_limit 在 [64M, 100M] 时, 内存使用淘汰到 avail_mem_limit-40M 时停止淘汰。
    • 当 avail_mem_limit 在 [100M, 5G] 时, 内存使用淘汰到 availmem_limit*0.6 时停止淘汰。
    • 当 avail_mem_limit 在 [5G, +∞] 时, 内存使用淘汰到 availmem_limit-2G 时停止淘汰。
    • 如果是达到记录数上限触发的淘汰则淘汰到 800 万行记录时停止淘汰。

2. sql_audit视图字段介绍

|-------------------------|---------------------|----------------------|
| 字段名称 | 类型 | 描述 |
| SVR_IP | varchar(32) | ip地址 |
| SVR_PORT | bigint(20) | 端口号 |
| REQUEST_ID | bigint(20) | 请求的id号 |
| TRACE_ID | varchar(128) | 这条语句的trace_id |
| CLIENT_IP | varchar(32) | 发送请求的client ip |
| CLIENT_PORT | bigint(20) | 发送请求的client port |
| TENANT_ID | bigint(20) | 发送请求的租户id |
| TENANT_NAME | varchar(64) | 发送请求的租户 名称 |
| USER_ID | bigint(20) | 发送请求的用户id |
| USER_NAME | varchar(64) | 发送请求的用户名称 |
| SQL_ID | varchar(32) | 这条SQL的id |
| QUERY_SQL | varchar(32768) | 实际的SQL语句 |
| PLAN_ID | bigint(20) | 执行计划id |
| AFFECTED_ROWS | bigint(20) | 影响行数 |
| RETURN_ROWS | bigint(20) | 返回行数 |
| PARTITION_CNT | bigint(20) | 该请求涉及的分区数 |
| RET_CODE | bigint(20) | 执行结果返回码 |
| EVENT | varchar(64) | 最长等待事件名称 |
| P1TEXT | varchar(64) | 等待事件参数1 |
| P1 | bigint(20) unsigned | 等待事件参数1的值 |
| P2TEXT | varchar(64) | 等待事件参数2 |
| P2 | bigint(20) unsigned | 等待事件参数2的值 |
| P3TEXT | varchar(64) | 等待事件参数3 |
| P3 | bigint(20) unsigned | 等待事件参数3的值 |
| LEVEL | bigint(20) | 等待事件的level级别 |
| WAIT_CLASS_ID | bigint(20) | 等待事件所属的class id |
| WAIT_CLASS# | bigint(20) | 等待事件所属的class 的下标 |
| WAIT_CLASS | varchar(64) | 等待事件所属的class 名称 |
| STATE | varchar(19) | 等待事件的状态 |
| WAIT_TIME_MICRO | bigint(20) | 该等待事件所等待的时间 |
| TOTAL_WAIT_TIME_MICRO | bigint(20) | 执行过程所有等待的总时间 |
| TOTAL_WAITS | bigint(20) | 执行过程总等待的次数 |
| RPC_COUNT | bigint(20) | 发送rpc个数 |
| PLAN_TYPE | bigint(20) | 执行计划类型 |
| IS_INNER_SQL | tinyint(4) | 是否内部sql请求 |
| IS_EXECUTOR_RPC | tinyint(4) | 当前请求是否rpc请求 |
| IS_HIT_PLAN | tinyint(4) | 是否命中plan_cache |
| REQUEST_TIME | bigint(20) | 开始执行时间点 |
| ELAPSED_TIME | bigint(20) | 接收到请求到执行结束消耗 总时间 |
| NET_TIME | bigint(20) | 发送rpc到接收到请求时间 |
| NET_WAIT_TIME | bigint(20) | 接收到请求到进入队列时间 |
| QUEUE_TIME | bigint(20) | 请求在队列等待事件 |
| DECODE_TIME | bigint(20) | 出队列后decode时间 |
| GET_PLAN_TIME | bigint(20) | 开始process到获得plan时间 |
| EXECUTE_TIME | bigint(20) | plan执行消耗时间 |
| APPLICATION_WAIT_TIME | bigint(20) unsigned | 所有application类事件的总时间 |
| CONCURRENCY_WAIT_TIME | bigint(20) unsigned | 所有concurrency类事件的总时间 |
| USER_IO_WAIT_TIME | bigint(20) unsigned | 所有user_io类事件的总时间 |
| SCHEDULE_TIME | bigint(20) unsigned | 所有schedule类事件的时间 |
| ROW_CACHE_HIT | bigint(20) | 行缓存命中次数 |
| BLOOM_FILTER_CACHE_HIT | bigint(20) | bloom filter缓存命中次数 |
| BLOCK_CACHE_HIT | bigint(20) | 块缓存命中次数 |
| BLOCK_INDEX_CACHE_HIT | bigint(20) | 块索引缓存命中次数 |
| DISK_READS | bigint(20) | 物理读次数 |
| EXECUTION_ID | bigint(20) | 执行ID |
| SESSION_ID | bigint(20) | session id |
| RETRY_CNT | bigint(20) | 重试次数 |
| TABLE_SCAN | tinyint(4) | 判断该请求是否含全表扫描 |
| CONSISTENCY_LEVEL | bigint(20) | 一致性级别 |
| MEMSTORE_READ_ROW_COUNT | bigint(20) | MEMSTORE中的读行数 |
| SSSTORE_READ_ROW_COUNT | bigint(20) | SSSTORE中读的行数 |
| REQUEST_MEMORY_USED | bigint(20) | 该请求消耗的内存 |

  • 一些重要的事件间隔

3. 基于sql_audit的诊断case

3.1. 最近100s某个租户的TOP SQL耗时监控

  • 检查语句:

    -- OceanBase 4.0.0.0及以上版本,请替换tenant_name的值为实际的租户名
    select /+read_consistency(weak),query_timeout(100000000)/ SQL_ID,count(1),avg(ELAPSED_TIME),avg(EXECUTE_TIME),avg(QUEUE_TIME),avg(AFFECTED_ROWS),avg(GET_PLAN_TIME)
    from gv$ob_sql_audit
    where time_to_usec(now(6))-request_time <1000000000
    and tenant_name='test_tenant'
    group by SQL_ID order by avg(ELAPSED_TIME)*count(1) desc limit 20;

    -- OceanBase 4.0.0.0以下版本,请替换tenant_name的值为实际的租户名
    select /+read_consistency(weak),query_timeout(100000000)/ SQL_ID,count(1),avg(ELAPSED_TIME),avg(EXECUTE_TIME),avg(QUEUE_TIME),avg(AFFECTED_ROWS),avg(GET_PLAN_TIME)
    from gv$sql_audit
    where time_to_usec(now(6))-request_time <1000000000
    and tenant_name='test_tenant'
    group by SQL_ID order by avg(ELAPSED_TIME)*count(1) desc limit 20 ;

  • 期望值: 观察SQL 整体耗时,cpu_time, 物理读及逻辑消耗是否合理,一般单行insert 和 主键查询 500us以内

  • 对应建议:通过SQL语义与表结构比对,确认执行计划是否合理,耗时是否正常

3.2. 查看集群中 SQL 请求流量是否均匀

  • **思路:**我们首先可以查出某个时间段内数据库中所有 SQL 并按照 server 级别进行聚合,再统计该时间段内每台机器上的 QPS。

  • 语句:

    -- OceanBase 4.0.0.0及以上版本,请替换t1.tenant_id的值为实际租户的值
    select t2.zone, t1.svr_ip, count(*) as QPS
    from oceanbase.gv$ob_sql_audit t1, oceanbase.__all_server t2
    where t1.svr_ip = t2.svr_ip and t1.tenant_id = 1001
    and IS_EXECUTOR_RPC = 0 and request_time > (time_to_usec(now()) - 1000000)
    and request_time < time_to_usec(now())
    group by t1.svr_ip order by QPS;

    -- OceanBase 4.0.0.0以下版本,请替换t1.tenant_id的值为实际租户的值
    select t2.zone, t1.svr_ip, count(*) as QPS
    from oceanbase.gv$ob_sql_audit t1, oceanbase.__all_server t2
    where t1.svr_ip = t2.svr_ip and t1.tenant_id = 1001
    and IS_EXECUTOR_RPC = 0 and request_time > (time_to_usec(now()) - 1000000)
    and request_time < time_to_usec(now())
    group by t1.svr_ip order by QPS;

3.3. 某个时间段请求次数排在 TOP-N 的 SQL

  • **思路:**我们首先可以查出某个时间段内数据库中所有 SQL 并按照 sql_id 级别进行聚合,再统计该时间段内每个SQL_ID的 QPS,取出top值。

  • 语句:

    -- OceanBase 4.0.0.0及以上版本,请替换tenant_id的值为实际租户的值
    select SQL_ID, count(*) as QPS, avg(t1.elapsed_time) RT
    from oceanbase.gv$ob_sql_audit t1
    where tenant_id = 1001 and IS_EXECUTOR_RPC = 0
    and request_time > (time_to_usec(now()) - 10000000)
    and request_time < time_to_usec(now())
    group by t1.sql_id order by QPS desc limit 10;

    -- OceanBase 4.0.0.0以下版本,请替换tenant_id的值为实际租户的值
    select SQL_ID, count(*) as QPS, avg(t1.elapsed_time) RT
    from oceanbase.gv$sql_audit t1
    where tenant_id = 1001 and IS_EXECUTOR_RPC = 0
    and request_time > (time_to_usec(now()) - 10000000)
    and request_time < time_to_usec(now())
    group by t1.sql_id order by QPS desc limit 10;

3.4. 定位所有SQL中消耗CPU最多的sql

**思路:**消耗CPU的时间是elapsed_time - queue_time,因为queue_time的过程中是在排队,并不消耗cpu. 排查消耗CPU最多的sql在cpu飙高的场景非常有用

语句:

-- OceanBase 4.0.0.0及以上版本,请替换tenant_id的值为实际租户的值
select sql_id, substr(query_sql, 1, 20) as query_sql, 
      sum(elapsed_time - queue_time) sum_t, count(*) cnt, 
      avg(get_plan_time), avg(execute_time)    
from oceanbase.gv$ob_sql_audit     
where   tenant_id = 1001      
       and request_time > (time_to_usec(now()) - 10000000)     
       and request_time < time_to_usec(now())
group by sql_id order by sum_t desc   limit 10;



-- OceanBase 4.0.0.0以下版本,请替换tenant_id的值为实际租户的值
select sql_id, substr(query_sql, 1, 20) as query_sql, 
      sum(elapsed_time - queue_time) sum_t, count(*) cnt, 
      avg(get_plan_time), avg(execute_time)    
from oceanbase.gv$sql_audit     
where   tenant_id = 1001      
       and request_time > (time_to_usec(now()) - 10000000)     
       and request_time < time_to_usec(now())
group by sql_id order by sum_t desc   limit 10;

3.5. 查看SQL的执行是否出现大量请求不合理的使用了远程执行

**思路:**sql_audit的PLAN_TYPE字段可以看到该SQL的执行计划类型,

  • plan_type=1 :本地执行计划。性能最好。
  • plan_type=2 : 远程执行计划。
  • plan_type=3 : 分布式执行计划。包含本地执行计划和远程执行计划。

一般情况下,如果出现远程执行比较多时可能时出现切主或proxy客户端路由不准的情况。

语句:

-- OceanBase 4.0.0.0及以上版本,请替换tenant_id的值为实际租户的值
select count(*), plan_type    
from oceanbase.gv$ob_sql_audit     
where tenant_id = 1001          
      and IS_EXECUTOR_RPC = 0          
      and request_time > (time_to_usec(now()) - 10000000)         
      and request_time < time_to_usec(now()) 
group by plan_type ;


-- OceanBase 4.0.0.0以下版本,请替换tenant_id的值为实际租户的值
select count(*), plan_type    
from oceanbase.gv$sql_audit     
where tenant_id = 1001          
      and IS_EXECUTOR_RPC = 0          
      and request_time > (time_to_usec(now()) - 10000000)         
      and request_time < time_to_usec(now()) 
group by plan_type ;

3.6. 查询全表扫描的SQL

**思路:**sql_audit的TABLE_SCAN字段是标识语句是否走了全表扫描,=1 表示全表扫描了。可以进一步分析一下SQL是否可以添加索引来防止全表扫描:

语句:

-- OceanBase 4.0.0.0及以上版本,请替换tenant_id的值为实际租户的值
select query_sql 
from oceanbase.gv$ob_sql_audit 
where table_scan = 1 and tenant_id = 1001 
group by sql_id;

-- OceanBase 4.0.0.0以下版本,请替换tenant_id的值为实际租户的值
select query_sql 
from oceanbase.gv$sql_audit 
where table_scan = 1 and tenant_id = 1001 
group by sql_id;

3.7 如何分析RT突然抖动的SQL?

在线上如果出现RT抖动,但RT并不是持续很高的情况,可以考虑在抖动出现后,立刻将sql audit关闭(alter system set ob_enable_sql_audit = 0),从而确保该抖动的SQL请求在sql audit中存在;然后通过3.3章节的【某个时间段请求次数排在 TOP-N 的 SQL】,分析有异常的SQL。

如果在sql_audit中找到了对应的RT异常请求,则可以分析该请求在sql audit中记录:

  • 查看retry次数是否很多(RETRY_CNT, 如果次数很多,则是否考虑是否有锁冲突或切主等情况)
  • 查看queue time是不是很大(QUEUE_TIME字段)
  • 查看获取执行计划时间(GET_PLAN_TIME), 如果时间很长,一般会伴随IS_HIT_PLAN = 0, 表示没有命中plan cache)
  • 查看EXECUTE_TIME是否很长,如果很长,则

a. 查看是否有很长等待事件耗时

b. 查看访问的行数是否很多, 看SSSTORE_READ_ROW_COUNT, MEMSTORE_READ_ROW_COUNT两个字段, 比如大小账号场景可能导致rt抖动。

|-----|------------------------------------------|
| 第一篇 | "神医"的修炼秘籍------《OceanBase诊断系列》之一 |
| 第二篇 | 一起走进sql_audit性能视图------《OceanBase诊断系列》之二 |

相关推荐
老年DBA2 天前
【无标题】observer: error while loading shared libraries: libmariadb.so.3处理办法
数据库·oceanbase
@素素~3 天前
OceanBase—02(入门篇——对于单副本单节点,由1个observer扩容为3个observer集群)——之前的记录,当初有的问题未解决,目前新版未尝试
数据库·oceanbase
OceanBase数据库官方博客3 天前
快手:数据库升级实践,实现PB级数据的高效管理|OceanBase案例
oceanbase·分布式数据库·实践经验
好记忆不如烂笔头abc5 天前
测试部署单副本 oceanbase-3.2.4.1 企业版
oceanbase
OceanBase数据库官方博客5 天前
利用 Local Data 导入文件到 OceanBase 的方法
银行·oceanbase·分布式数据库·保险证券
OceanBase数据库官方博客5 天前
遇到慢SQL、SQL报错,应如何快速定位问题 | OceanBase优化实践
sql·oceanbase·分布式数据库·实践经验
救救孩子把5 天前
OceanBase企业级分布式关系数据库
数据库·分布式·oceanbase
OceanBase数据库官方博客6 天前
DDL 超时,应该如何解决 | OceanBase 用户问题集萃
oceanbase·分布式数据库·ddl
光锥智能6 天前
OceanBase云数据库战略实施两年,受零售、支付、制造行业青睐
数据库·oceanbase·零售
爬树的小蚂蚁6 天前
OceanBase 一级表分区记录
oceanbase