技術分享 | OceanBase 慢查詢排查思路

作者:任仲禹

愛可生 DBA 團隊成員,擅長故障分析和性能優化,文章相關技術問題,歡迎大家一起討論。

本文來源:原創投稿

* 愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。



本文彙總了項目實踐中前輩的經驗和筆者的理解,旨在幫助初學 OceanBase(以下簡稱 OB)的工程師,快速解決 SQL 執行緩慢等性能問題。當遇到性能問題時,很多工程師可能會感到無從下手,本文將根據關鍵日誌提供多種分析方向,以加速問題排查。

背景

應用連接 OB 的生產架構,一般有兩種:

  1. 應⽤ -> OBProxy -> OBServer
  2. 應⽤ -> OBProxy-Sharding -> OBServer

前者是大多數客戶使⽤場景,後者是少數客戶使⽤的單元化架構場景,後文將 OBProxy 和 OBProxy-Sharding 統稱爲 ODP(OceanBase Database Proxy)。

當我們發現某條語句耗時較長時,我們需要排查的點有:應⽤到 ODP 的⽹絡時間、ODP 的執行時間、ODP 到 OBServer 的⽹絡時間、OBServer 的執行時間。

從哪些信息入手?

要診斷哪部分時間消耗長,以及原因是什麼,大多數情況會從如下幾個組件獲取信息。

ODP 組件

  • obproxy_digest.log:審計⽇志,記錄執⾏失敗的 SQL 語句、執行時間大於參數 query_digest_time_threshold 閾值(默認是 2ms)請求。
  • obproxy_slow.log:慢 SQL 請求日誌,記錄執⾏時間大於參數 slow_query_time_threshold 閾值(默認是 500ms)的請求。
  • obproxy.log:ODP 總日誌。

obproxy_digest.logobproxy_slow.log 中,第 15、16、17、18 列(即 8353us,179us,0us,5785us)分別表示:ODP 處理總時間、ODP 預處理時間、ODP 獲取連接時間、OBServer 執⾏時間。示例如下:

2023-05-04
16:46:03.513268,test_obproxy,,,,test:ob_mysql:sbtest,OB_MYSQL,sbtest1,sbtest1,COM_QUERY
,SELECT,failed,1064,select t1.*%2Ct2.* from sbtest1 t1%2Csbtest2 t2 where t1.id = t2.id
where id <10000,8353us,179us,0us,5785us,Y0-7FA25BB4A2E0,YB420ABA3FAC-0005FA2415BE0F81-
0-0,,,0,10.186.63.172:2881
  • ODP 處理總時間的起點:ODP 接收到客戶端請求的時間;
  • ODP 處理總時間的終點:ODP 把所有的數據都寫回給客戶端;
  • ODP 預處理時間:包含去 oceanbase.__all_virtual_proxy_schema 查詢 Leader 的時間;
  • ODP 獲取連接時間:目前不做記錄,看到的都是 0;
  • OBServer 執行時間:起點是 ODP 發送請求給 OBServer,終點是收到  OBServer 返回第一條記錄。

從上面的原理可以看出,後三項時間相加並不等於第一項時間,比如 ODP 處理總時間比較長,但是預處理時間和 OBServer 執行時間都很短,有可能時間消耗在 OBServer 將第一條記錄返回給 ODPServer 和 ODPServer 把所有數據寫回給客戶端之間,這在結果集較大的 SQL 語句中⽐較常⻅。

OBserver 組件

  • gv$audit_sql:該視圖⽤於展示所有 OBServer 上每⼀次 SQL 請求的來源、執⾏狀態等統計信息。

該視圖是按照租戶拆分的,除了系統租戶,其他租戶不能跨租戶查詢。⼀般常⽤的字段有:request_timesql_id plan_idplan_typetrace_idsvr_ipclient_ipuser_client_ipuser_namedb_nameelapsed_timequeue_timeget_plan_timeexecute_timeretry_cnttable_scanret_codequery_sql……

# 大致的歸類如下
標識信息:tenant_id,sql_id,trace_id,plan_id ,sid,transaction_hash,......
來自哪⾥:user_name,user_client_ip,client_ip(OBProxy) ,......
在哪執行:svr_ip,db_name,plan_type, ......
開始時間:request_time
執行耗時:elapsed_time,get_plan_time,execute_time ,......
等待耗時:total_wait_time_micro,queue_time,net_time,user_io_wait_time,......
數據掃描:table_scan(全表掃描),disk_reads,memstore_read_row_count,sstable_read_row_count ,......
並行執行:expected_worker_count,used_worker_count, qc_id,sqc_id,worker_id ,......
請求類型:request_type, ......
強弱讀:consistency_level
數據量:affected_rows,return_rows,partition_cnt,......
返回碼:ret_code
  • observer.log:OBServer 運行的主要⽇志,這裏面的信息非常全面,外部用戶不易解讀,很多情況下會根據 trace_id 去搜索,例如通過 OCP 的 SQL 診斷功能獲取到 TraceID ,再進⾏查詢。

常見 OB 慢查詢分析思路

1. ODP 給應用回寫數據耗時長

當 SQL 的結果集很大,ODP 就需要較長時間將數據返回給應用,這時候會發現 OBServer 執行時間和 ODP 預處理時間相加,比 ODP 執行總時間要小,以下面的 obproxy.log 記錄爲例:

[2023-04-19 19:12:31.662602] WARN [PROXY.SM] update_cmd_stats (ob_mysql_sm.cpp:8633)
[5628][Y0-7F820F6C7960] [lt=38] [dc=0] Slow Query: ((client_ip={x.x.x.x:51555},
server_ip={x.x.x.x:2881}, obproxy_client_port={x.x.x.x:33584},
server_trace_id=YB420A97B009-0005F6EF28FSFS11-0-0, route_type=ROUTE_TYPE_LEADER,
user_name=depo, tenant_name=su, cluster_name=cmcluster, logic_database_name=,
logic_tenant_name=, ob_proxy_protocol=0, cs_id=1077902,
proxy_sessid=1513983664671181892, ss_id=611834, server_sessid=3221841415, sm_id=260155,
cmd_size_stats={client_request_bytes:87, server_request_bytes:122,
server_response_bytes:0, client_response_bytes:185002181}, cmd_time_stats=
{client_transaction_idle_time_us=0, client_request_read_time_us=11,
client_request_analyze_time_us=10, cluster_resource_create_time_us=0,
pl_lookup_time_us=4, pl_process_time_us=4, congestion_control_time_us=1,
congestion_process_time_us=0, do_observer_open_time_us=2, server_connect_time_us=0,
server_sync_session_variable_time_us=0, server_send_saved_login_time_us=0,
server_send_use_database_time_us=0, server_send_session_variable_time_us=0,
server_send_all_session_variable_time_us=0, server_send_last_insert_id_time_us=0,
server_send_start_trans_time_us=0, build_server_request_time_us=2,
plugin_compress_request_time_us=0, prepare_send_request_to_server_time_us=65,
server_request_write_time_us=20, server_process_request_time_us=337792,
server_response_read_time_us=2353609, plugin_decompress_response_time_us=1299449,
server_response_analyze_time_us=17505, ok_packet_trim_time_us=0,
client_response_write_time_us=1130104, request_total_time_us=5309727}, sql=SELECT x,x,x
FROM sbtest.sbtest1 where id =1)
  • client_response_bytes:185002181
  • client_response_write_time_us=1130104

該示例中,ODP 回寫給應用的數據爲 185MB,耗時 1.1s,可以通過該信息觀測下是否是 SQL 的結果集較大。

2. ODP 獲取 location cache 慢

ODP 要把 SQL 路由到準確的 OBServer 上,只需要知道每個 Table 的 Partition 的 Leader 所在位置,獲取位置的過程叫做 “get location cache”。通常這個過程很快,並且獲取後會緩存在本地,少數情況下,這個時間消耗會慢,以下面爲例:

[2023-05-07 00:01:04.506809] WARN [PROXY.SM] update_cmd_stats (ob_mysql_sm.cpp:8607)
[363][Y0-7F4521AA21A0] [lt=28] [dc=0] Slow Query: ((client_ip={x.x.x.x:36246},
server_ip={x.x.x.x:2881}, obproxy_client_port={21.2.1
92.29:40556}, server_trace_id=, route_type=ROUTE_TYPE_LEADER, user_name=mY14OyQ1tF,
tenant_name=bu06, cluster_name=cscluster2, logic_database_name=budb,
logic_tenant_name=odp-h170kfw30w7l, ob_proxy_protocol=2, cs_id=2993079,
proxy_sessid=1513983656080750373, ss_id=53737247, server_sessid=3223571471,
sm_id=44290320, cmd_size_stats={client_request_bytes:342, server_request_bytes:385,
server_response_bytes:66, client_response_bytes:66}, cmd_time_stats=
{client_transaction_idle_time_us=0, client_request_read_time_us=25,
client_request_analyze_time_us=25, cluster_resource_create_time_us=0,
pl_lookup_time_us=4998993, pl_process_time_us=126, congestion_control_time_us=2,
congestion_process_time_us=0, do_observer_open_time_us=5, server_connect_time_us=0,
server_sync_session_variable_time_us=0, server_send_saved_login_time_us=0,
server_send_use_database_time_us=0, server_send_session_variable_time_us=0,
server_send_all_session_variable_time_us=0, xxxxxxxx
  • pl_lookup_time_us=4998993

耗時 4s 明顯有異常,獲取到該日誌後可以快速和 OB 研發縮小問題排查範圍。

3. 表的路由選擇

在 OceanBase 數據庫中,有 Local 計劃、Remote 計劃和 Distributed 計劃三種表路由。Local 計劃、Remote 計劃均爲單分區的路由。ODP 的作⽤就是儘量消除 Remote 計劃,將路由儘可能的變爲 Local 計劃。

如果表路由類型爲 Remote 計劃的 SQL 過多,則表示該 SQL 性能可能不是最優,通常的原因有 ODP 路由問題、無法計算表分區 ID、使用了全局索引、需要開啓二次路由等等。

通過 gv$sql_auditPLAN_TYPE 字段可以判斷 SQL 的執行計劃類型:

  • 1:Local
  • 2:Remote
  • 3:Distributed

4. OBServer 寫入限速

當 memstore 已使⽤的內存達到 writing_throttling_trigger_percentage 時(默認 100),觸發寫入限速。當該配置項的值爲 100 時,表示關閉寫入限速機制。在觸發寫入限速後,剩餘 memstore 內存必須保證在writing_throttling_maximum_duration(默認 1h)內不會分配完,也就是寫入速度上限爲 memstore * (1- writing_throttling_trigger_percentage) / writing_throttling_maximum_duration

通過監控 gv$memstore 可以知道 memstore 使用的百分比。當發生了寫入限速,observer.log 中會看到如下記錄:

[2023-04-10 10:52:09.076066] INFO [COMMON] ob_fifo_arena.cpp:301 [68425][1739]
[YB420A830ADF-00058B41370AAF4F] [lt=85] [dc=0] report write throttle
info(cur_mem_hold=162644623360, throttle_info_={decay_factor_:"0.000000005732",
alloc_duration_:2400000000, trigger_percentage_:70, memstore_threshold_:231928233960,
period_throttled_count_:140, period_throttled_time_:137915965,
total_throttled_count_:23584, total_throttled_time_:27901629728})

關鍵字:report,write,throttle,info

還有⼀種場景就是發現 QPS 異常下降時(尤其是包含⼤量寫⼊,可以通過查詢系統表的⽅式確認是否是由於寫⼊限速導致。詳見:技術分享 | OceanBase寫入限速源碼解讀

select * from v$session_event where EVENT='memstore memory page alloc wait' \G;
*************************** 94. row ***************************
 CON_ID: 1
 SVR_IP: x.x.x.x
 SVR_PORT: 22882
 SID: 3221487713
 EVENT: memstore memory page alloc wait
 TOTAL_WAITS: 182673
 TOTAL_TIMEOUTS: 0
 TIME_WAITED: 1004.4099
 AVERAGE_WAIT: 0.005498403704981032
 MAX_WAIT: 12.3022
 TIME_WAITED_MICRO: 10044099
 CPU: NULL
 EVENT_ID: 11015
 WAIT_CLASS_ID: 109
 WAIT_CLASS#: 9
 WAIT_CLASS: SYSTEM_IO

關鍵字:memstore,memory,page,alloc,wait

5. 訪問執行計劃

訪問計劃也是影響 SQL 耗時的⼀個因素,沒有命中 plan cache、訪問計劃發生了預期外的變化都會造成 SQL 執行變慢。

沒有命中 plan cache 可以在 gv$sql_audit 中看到 IS_HIT_PLAN=0

要查看 SQL 具體的執行計劃有兩種⽅式:一是執行 explain extended <query_sql>,但這隻能看到當前環境下,該語句的執行計劃,可能並不是現場緩慢 SQL 的執行計劃,需要查看緩慢 SQL 正在使用的訪問計劃,需要首先記錄下 gv$sql_audit 中的四個值:SVR_IPSVR_PORTTENANT_IDPLAN_ID。並在 gv$plan_cache_plan_explain中進行查詢:

select SVR_IP, SVR_PORT, TENANT_ID, PLAN_ID from gv$sql_audit where query_sql ...
select * from gv$plan_cache_plan_explain where ip=<SVR_IP> and port=<SVR_PORT> and
tenant_id=<TENANT_ID> and plan_id=<PLAN_ID>

6. OBServer 鎖等待

OceanBase 選擇 MVCC 來實現事務併發性和一致性,支持讀寫不互斥。因此事務間的鎖等待一般發生在寫請求上(lock_for_write),極少情況下也會發生在讀請求(lock_for_shared)。

當發生了鎖等待,SQL執⾏耗時也會變長,通常的表現是:在 gv$sql_audit 中看到 elapsed_time 較大,execute_time 較小,retry_cnt 較大(>0),伴隨 observer.log 可以觀察到如下日誌:

[2023-03-29 12:00:26.310172] WARN [STORAGE.TRANS] on_wlock_retry
(ob_memtable_context.cpp:393) [135700][2338][Y1312AC1C4140-0005EFC759EADC21] [lt=10]
[dc=0] lock_for_write conflict(*this=alloc_type=0 ctx_descriptor=700817166
trans_start_time=1680062426310071 min_table_version=1679627152331552
max_table_version=1679627152331552 is_safe_read=false has_read_relocated_row=false
read_snapshot=1680062426310007 start_version=-1 trans_version=9223372036854775807
commit_version=0 stmt_start_time=1680062426310074 abs_expired_time=1680062436209982
stmt_timeout=9899908 abs_lock_wait_timeout=1680062436209982 row_purge_version=0
lock_wait_start_ts=0 trx_lock_timeout=-1 end_code=0 is_readonly=false ref=2 pkey=
{tid:1116004302242691, partition_id:0, part_cnt:0} trans_id={hash:4021727895899886621,
inc:669379877, addr:"172.28.65.64:4882", t:1680062426310046} data_relocated=0
relocate_cnt=0 truncate_cnt=0 trans_mem_total_size=0 callback_alloc_count=0
callback_free_count=0 callback_mem_used=0 checksum_log_ts=0,
key=table_id=1116004302242691 rowkey_object=[{"BIGINT":2024021}] ,
conflict_ctx="alloc_type=0 ctx_descriptor=700817301 trans_start_time=1680062426309892
xx
關鍵字:lock_for_write,conflict

7. SQL 語句有問題

一般 SQL 語句查詢慢排除上述問題後,大部分跟自身有關,例如 SQL 語句沒有走到索引、寫法有問題等。這種情況就需要:

  1. 通過 gv$sql_audit 表或 ODP 日誌拿到具體的 SQL 文本。
# 查詢以某個租戶⼀段範圍內執⾏耗時的SQL語句進⾏排序
SELECT usec_to_time(request_time) as request_time,
sql_id, plan_id, plan_type, trace_id,
svr_ip, client_ip, user_client_ip, user_name, db_name elapsed_time, queue_time,
get_plan_time, execute_time, retry_cnt, table_scan,
ret_code,
query_sql
FROM gv$sql_audit
WHERE tenant_id=1001
AND request_time BETWEEN time_to_usec('2023_05_12 13:00:00')
AND time_to_usec('2023_05_13 13:10:00'AND is_executor_rpc = 0
ORDER BY elapsed_time DESC limit 10;
  1. 拿到 SQL 文本後,再通過 Explain 查詢計劃進⾏分析(例如對下文語句進⾏ Explain 分析,比如 name 中只有表名不包含索引列的話,則該 SQL 語句可能使用的主鍵或全表掃描)。
obclient [sbtest]> explain select * from sbtest1 where k like '%111181823%' \G
*************************** 1. row ***************************
Query Plan: ========================================
|ID|OPERATOR |NAME |EST. ROWS|COST |
----------------------------------------
|0 |TABLE SCAN|sbtest1|283098 |333648|
========================================
Outputs & filters:
-------------------------------------
0 - output([sbtest1.id], [sbtest1.k], [sbtest1.c], [sbtest1.pad]),
filter([(T_OP_LIKE, cast(sbtest1.k, VARCHAR(1048576)), '%111181823%''\\')]),
 access([sbtest1.k], [sbtest1.id], [sbtest1.c], [sbtest1.pad]), partitions(p0)
1 row in set (0.004 sec)
  1. 排查 SQL 成本和執行計劃中訪問順序是否有問題,就不具體展開了。

以上就是導致 OB 慢查詢常見的原因及分析思路,希望對讀者有所幫助。


本文關鍵字 :#OceanBase# #慢查詢日誌#

文章推薦:

技術分享 | OceanBase寫入限速源碼解讀

故障分析 | innodb_thread_concurrency 導致數據庫異常的問題分析

故障分析 | OceanBase 頻繁更新數據後讀性能下降的排查

故障分析 | MySQL 升級到 8.0 變慢問題分析

技術分享 | 一招解決 MySQL 中 DDL 被阻塞的問題

故障分析 | 一條本該記錄到慢日誌的 SQL 是如何被漏掉的



關於 SQLE

愛可生開源社區的 SQLE 是一款面向數據庫使用者和管理者,支持多場景審覈,支持標準化上線流程,原生支持 MySQL 審覈且數據庫類型可擴展的 SQL 審覈工具。

SQLE 獲取

類型 地址
版本庫 https://github.com/actiontech/sqle
文檔 https://actiontech.github.io/sqle-docs-cn/
發佈信息 https://github.com/actiontech/sqle/releases
數據審覈插件開發文檔 https://actiontech.github.io/sqle-docs-cn/3.modules/3.7_auditplugin/auditplugin_development.html
提交有效 pr,高質量 issue,將獲贈面值 200-500 元(具體面額依據質量而定)京東卡以及愛可生開源社區精美周邊!
更多關於 SQLE 的信息和交流,請加入官方QQ交流羣:637150065

本文分享自微信公衆號 - 愛可生開源社區(ActiontechOSS)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章