作者:爱可生数据库高级工程师任仲禹,擅长故障分析和性能优化。
本文通过一个案例,分享使用OceanBase时,SQL走错执行计划,而导致慢SQL的排查方法论。
案例背景
在使用OceanBase 3.2.3 版本的过程中,项目组反映某个 SELECT 语句在指定时间内的查询响应速度异常缓慢,其耗时远超正常情况的1000倍以上。具体细节如下:
- 慢 SELECT
SELECT COUNT(*) AS TOT_CNT FROM renzy WHERE (ACCT_NO = '6222620117273900882') AND LAWENF_NTIST_TP_CD NOT LIKE '12%' AND LAWENF_NTIST_TP_CD NOT LIKE '05%' AND EMRG_STPY_SRC_CD != 'JZ05' AND ACCTG_DT >= '1900-01-01' AND ACCTG_DT <= '2025-03-31' ;
- 关键表结构、记录数信息如下
-- 脱敏处理
show create table renzy\G
*************************** 1. row ***************************
Table: renzy
Create Table: CREATE TABLE `renzy` (
`ID` char(18) COLLATE utf8mb4_bin NOT NULL COMMENT ,
...
`ACCT_NO` char(40) COLLATE utf8mb4_bin NOT NULL COMMENT ,
...
`ACCTG_DT` date DEFAULT NULL COMMENT ,
...
PRIMARY KEY (`ID`),
...
KEY `renzy_I2` (`ACCT_NO`) BLOCK_SIZE 16384 LOCAL,
...
KEY `renzy_I5` (`ACCTG_DT`, `ENQ_INST_CD`, `BLON_INST_CD`, `EMRG_STPY_SRC_CD`) BLOCK_SIZE 16384 LOCAL,
...
) DEFAULT CHARSET = utf8mb4;
1 row in set (0.01 sec)
MySQL > SELECT COUNT(*) AS TOT_CNT FROM renzy WHERE (ACCT_NO = '6222620117273900882');
+---------+
| TOT_CNT |
+---------+
| 1 |
+---------+
1 row in set (0.02 sec)
MySQL > SELECT COUNT(*) AS TOT_CNT FROM renzy WHERE ACCTG_DT >= '1900-01-01' AND ACCTG_DT <= '2025-03-31';
+----------+
| TOT_CNT |
+----------+
| 25432155 |
+----------+
1 row in set (12.42 sec)
MySQL > SELECT COUNT(*) AS TOT_CNT FROM renzy;
+----------+
| TOT_CNT |
+----------+
| 25435024 |
+----------+
1 row in set (10.65 sec)
排查过程
正常执行不慢
MySQL > select * from (SELECT COUNT(*) AS TOT_CNT FROM renzy WHERE (ACCT_NO = '6222620117273900882') AND LAWENF_NTIST_TP_CD NOT LIKE '12%' AND LAWENF_NTIST_TP_CD NOT LIKE '05%' AND EMRG_STPY_SRC_CD != 'JZ05' AND ACCTG_DT >= '1900-01-01' AND ACCTG_DT <= '2025-03-31') as orginal limit 2000; select last_trace_id();
+---------+
| TOT_CNT |
+---------+
| 1 |
+---------+
1 row in set (0.02 sec)
以下是执行计划,从中可见,索引I2是最高效的选择,它在进行等值匹配时仅需要执行一次回表操作。
*************************** 1. row ***************************
Query Plan: ========================================================
|ID|OPERATOR |NAME |EST. ROWS|COST|
--------------------------------------------------------
|0 |LIMIT | |1 |92 |
|1 | SCALAR GROUP BY| |1 |92 |
|2 | TABLE SCAN |renzy(renzy_I2)|1 |92 |
========================================================
...
Outline Data:
-------------------------------------
/*+
BEGIN_OUTLINE_DATA
INDEX(@"SEL$2" "gabsdb.renzy"@"SEL$2" "renzy_I2")
END_OUTLINE_DATA
*/
...
renzy:table_rows:25419080, physical_range_rows:1, logical_range_rows:1, index_back_rows:1, output_rows:0, est_method:local_storage, optimization_method=cost_based, avaiable_index_name[renzy_I2,renzy_I5], pruned_index_name[renzy_I1,renzy_I3,renzy_I4,renzy_I6], unstable_index_name[renzy], estimation info[table_id:1105009185965290, (table_type:1, version:0-1699898410195654-1699898410195654, logical_rc:1, physical_rc:1), (table_type:7, version:1699898401860480-1699898401860480-1699898433101378, logical_rc:0, physical_rc:0), (table_type:7, version:1699898433101378-1699904137032515-1699905915658079, logical_rc:0, physical_rc:0), (table_type:5, version:1699898433101378-1699904137032515-1699905915658079, logical_rc:0, physical_rc:0), (table_type:0, version:1699905915658079-1699905915658079-9223372036854775807, logical_rc:0, physical_rc:0)]
...
通过 OCP 的 SQL 诊断获取慢 SQL 的 plan_id,检查慢 SQL 实际命中的 plan。
MySQL [oceanbase]> select * from gv$plan_cache_plan_stat where plan_id=7288229 \G
*************************** 1. row ***************************
...
plan_id: 7288229
...
statement: select * from (SELECT COUNT(*) AS TOT_CNT FROM renzy WHERE (ACCT_NO = ?) AND LAWENF_NTIST_TP_CD NOT LIKE ? AND LAWENF_NTIST_TP_CD NOT LIKE ? AND EMRG_STPY_SRC_CD != ? AND ACCTG_DT >= ? AND ACCTG_DT <= ?) as orginal limit 2000
query_sql: select * from (SELECT COUNT(*) AS TOT_CNT FROM renzy WHERE (ACCT_NO = '6222620117273900882') AND LAWENF_NTIST_TP_CD NOT LIKE '12%' AND LAWENF_NTIST_TP_CD NOT LIKE '05%' AND EMRG_STPY_SRC_CD != 'JZ05' AND ACCTG_DT >= '' AND ACCTG_DT <= '') as orginal limit 2000
special_params: 2000
param