基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
x86 切换 ARM 后参数相同的高频 SQL 走 SSTable 单行读取并回表,引发 CAS 原子竞争导致 CPU 打满
更新时间:2026-08-25 06:51
问题现象
集群从 x86 切换至 ARM 后,核心业务队列积压,当时单表 T_MAIN 的 Leader 30 节点 CPU 打满。
SQL 响应时间达几百毫秒。
查看当时 30 节点上已经存在严重的队列积压,且积压在 queue4,即 normal SQL 队列。
通过 processlist 观察到几乎为 DE3E302D210B5E8EED200B62C28C189D 这条 SQL ID,该 SQL 为一条 select 33 列的查询语句:
select t.COL_01 as COL_01_ALIAS, t.COL_02 as COL_02_ALIAS, ... , t.COL_33 as COL_33_ALIAS from T_MAIN t where (t.COL_16=? and t.COL_18<>?)
结合现场查询情况,该慢 SQL 的业务传参几乎一样,查询条件为 t.COL_16=99100000 且 t.COL_18<>3。
执行计划在 Where 条件的扫描上,最终返回 551 行,时间开销均在执行时间上,并未发现计划不合理之处,且观察了 x86 环境计划也无不同之处。执行计划如下:
obclient [oceanbase]> select * from gv$plan_cache_plan_explain where ip='x.x.x.x' and port=2882 and tenant_id=1012 and plan_id=12947;
+-----------+-------------+------+---------+------------+--------------+----------------+----------------------------------+------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TENANT_ID | IP | PORT | PLAN_ID | PLAN_DEPTH | PLAN_LINE_ID | OPERATOR | NAME | ROWS | COST | PROPERTY |
+-----------+-------------+------+---------+------------+--------------+----------------+----------------------------------+------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1012 | x.x.x.x | 2882 | 12947 | 0 | 0 | PHY_TABLE_SCAN | T_MAIN(IDX_COL_02_16) | 551 | 3603 | table_rows:317658, physical_range_rows:551, logical_range_rows:551, index_back_rows:551,output_rows:551, est_method:local_storage, avaiable_index_name[IDX_COL_02_16], pruned_index_name[IDX_COL_14,IDX_COL_13,IDX_COL_02,UK_T_MAIN_CODE,IDX_COL_07_08,T_MAIN], estimationinfo[table_id:1112705767468312, (table_type:1, version:0-1779758531565485-1779758531565485, logical_rc:551, physical_rc:551), (table_type:0, version:1779733298299187-1779733298299187-9223372036854775807, logical_rc:0, physical_rc:0)] |
+-----------+-------------+------+---------+------------+--------------+----------------+----------------------------------+------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.009 sec)
===============================================================
|ID|OPERATOR |NAME |EST. ROWS|COST|
---------------------------------------------------------------
|0 |TABLE SCAN|T_MAIN(IDX_COL_02_16) |571 |3898|
===============================================================
Outputs & filters:
-------------------------------------
0 - output([THIS_.COL_01(0xff9f47b6b9d0)], [THIS_.COL_15(0xff9f47b6c000)], [THIS_.COL_14(0xff9f47b6c630)], [THIS_.COL_13(0xff9f47b6cc60)], [THIS_.COL_12(0xff9f47b6d290)], [THIS_.COL_11(0xff9f47b6d8c0)], [THIS_.COL_17(0xff9f47b6def0)],
[THIS_.COL_18(0xff9f47b6e520)], [THIS_.COL_06(0xff9f47b6eb50)], [THIS_.COL_07(0xff9f47b6f180)], [THIS_.COL_10(0xff9f47b6f7b0)], [THIS_.COL_08(0xff9f47b6fde0)], [THIS_.COL_09(0xff9f47b70410)], [THIS_.COL_05(0xff9f47b70a40)],
[THIS_.COL_04(0xff9f47b71070)], [THIS_.COL_02(0xff9f47b68a20)], [THIS_.COL_03(0xff9f47b739e0)], [THIS_.COL_16(0xff9f47b69b20)], [THIS_.COL_21(0xff9f47b82350)], [THIS_.COL_25(0xff9f47b82980)], [THIS_.COL_26(0xff9f47b82fb0)],
[THIS_.COL_27(0xff9f47b835e0)], [THIS_.COL_28(0xff9f47b83c10)], [THIS_.COL_29(0xff9f47b84240)], [THIS_.COL_30(0xff9f47b84870)], [THIS_.COL_31(0xff9f47b84ea0)], [THIS_.COL_19(0xff9f47b854d0)], [THIS_.COL_20(0xff9f47b85b00)],
[THIS_.COL_32(0xff9f47b86130)], [THIS_.COL_34(0xff9f47b86760)], [THIS_.COL_33(0xff9f47b86d90)], [THIS_.COL_23(0xff9f47b873c0)], [THIS_.COL_22(0xff9f47b879f0)]), filter([cast(cast(THIS_.COL_16(0xff9f47b69b20), VARCHAR2(3 BYTE))(0xff9f47b69e60),
NUMBER(-1, -85))(0xff9f47b6a7f0) != 3(0xff9f47b69400)]),
access([THIS_.COL_02(0xff9f47b68a20)], [THIS_.COL_16(0xff9f47b69b20)], [THIS_.COL_01(0xff9f47b6b9d0)], [THIS_.COL_15(0xff9f47b6c000)], [THIS_.COL_14(0xff9f47b6c630)], [THIS_.COL_13(0xff9f47b6cc60)], [THIS_.COL_12(0xff9f47b6d290)],
[THIS_.COL_11(0xff9f47b6d8c0)], [THIS_.COL_17(0xff9f47b6def0)], [THIS_.COL_18(0xff9f47b6e520)], [THIS_.COL_06(0xff9f47b6eb50)], [THIS_.COL_07(0xff9f47b6f180)], [THIS_.COL_10(0xff9f47b6f7b0)], [THIS_.COL_08(0xff9f47b6fde0)],
[THIS_.COL_09(0xff9f47b70410)], [THIS_.COL_05(0xff9f47b70a40)], [THIS_.COL_04(0xff9f47b71070)], [THIS_.COL_03(0xff9f47b739e0)], [THIS_.COL_21(0xff9f47b82350)], [THIS_.COL_25(0xff9f47b82980)], [THIS_.COL_26(0xff9f47b82fb0)],
[THIS_.COL_27(0xff9f47b835e0)], [THIS_.COL_28(0xff9f47b83c10)], [THIS_.COL_29(0xff9f47b84240)], [THIS_.COL_30(0xff9f47b84870)], [THIS_.COL_31(0xff9f47b84ea0)], [THIS_.COL_19(0xff9f47b854d0)], [THIS_.COL_20(0xff9f47b85b00)],
[THIS_.COL_32(0xff9f47b86130)], [THIS_.COL_34(0xff9f47b86760)], [THIS_.COL_33(0xff9f47b86d90)], [THIS_.COL_23(0xff9f47b873c0)], [THIS_.COL_22(0xff9f47b879f0)]), partitions(p0),
is_index_back=true, filter_before_indexback[true],
range_key([THIS_.COL_02(0xff9f47b68a20)], [THIS_.COL_16(0xff9f47b69b20)], [THIS_.COL_01(0xff9f47b6b9d0)]), range(99100000,MIN,MIN ; 99100000,MAX,MAX),
range_cond([THIS_.COL_02(0xff9f47b68a20) = 99100000(0xff9f47b68300)])
Used Hint:
-------------------------------------
/*+
*/
Outline Data:
-------------------------------------
/*+
BEGIN_OUTLINE_DATA
INDEX(@"SEL$1" "SCHEMA_A.THIS_"@"SEL$1" "IDX_COL_02_16")
END_OUTLINE_DATA
*/
Plan Type:
-------------------------------------
LOCAL
Optimization Info:
-------------------------------------
THIS_:table_rows:317658, physical_range_rows:1142, logical_range_rows:1142, index_back_rows:571, output_rows:571, est_method:local_storage, optimization_method=cost_based,
avaiable_index_name[IDX_COL_02,IDX_COL_02_16,T_MAIN], pruned_index_name[IDX_COL_14,IDX_COL_13,UK_T_MAIN_CODE,IDX_COL_07_08], estimation info[table_id:1112705767468312, (table_type:1,
version:0-1779758531565485-1779758531565485, logical_rc:1142, physical_rc:1142), (table_type:0, version:1779733298299187-1779733298299187-9223372036854775807, logical_rc:0, physical_rc:0)]
Parameters:
过滤日志,查找 CPU 打高期间对应该条 SQL 的 slow query 信息,发现从回表获取行到结果集关闭(即索引回表发送数据)这段路径耗时比较长,说明耗时主要发生在索引回表及数据发送期间。
现场备份的原表结构如下:
CREATE TABLE "T_MAIN" (
"COL_01" NUMBER(20) CONSTRAINT "T_MAIN_OBNOTNULL_1721354162376758" NOT NULL ENABLE,
"COL_02" NUMBER(20),
"COL_03" NUMBER(20),
"COL_04" VARCHAR2(3),
"COL_05" VARCHAR2(6),
"COL_06" VARCHAR2(64),
"COL_07" VARCHAR2(256),
"COL_08" VARCHAR2(512),
"COL_09" VARCHAR2(16),
"COL_10" VARCHAR2(8),
"COL_11" NUMBER(20),
"COL_12" VARCHAR2(256),
"COL_13" NUMBER(10),
"COL_14" VARCHAR2(256),
"COL_15" NUMBER(20),
"COL_16" VARCHAR2(3),
"COL_17" VARCHAR2(1024),
"COL_18" NUMBER(5),
"COL_19" NUMBER(20),
"COL_20" DATE,
"COL_21" VARCHAR2(1),
"COL_22" VARCHAR2(32),
"COL_23" VARCHAR2(32),
"COL_24" NUMBER(1),
"COL_25" VARCHAR2(50),
"COL_26" VARCHAR2(50),
"COL_27" VARCHAR2(50),
"COL_28" VARCHAR2(1),
"COL_29" DATE,
"COL_30" NUMBER(20),
"COL_31" NUMBER(20),
"COL_32" NUMBER(20),
"COL_33" VARCHAR2(256),
"COL_34" DATE,
"COL_35" VARCHAR2(32),
"COL_36" VARCHAR2(64),
"COL_37" NUMBER(20),
"COL_38" NUMBER(20),
CONSTRAINT "PK_T_MAIN" PRIMARY KEY ("COL_01"),
CONSTRAINT "UK_T_MAIN_CODE" UNIQUE ("COL_07")
) COMPRESS FOR ARCHIVE REPLICA_NUM = 3 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0;
CREATE INDEX "SCHEMA_A"."IDX_COL_14" ON "SCHEMA_A"."T_MAIN" (
"COL_14"
) GLOBAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_13" ON "SCHEMA_A"."T_MAIN" (
"COL_13"
) GLOBAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_02" ON "SCHEMA_A"."T_MAIN" (
"COL_02"
) GLOBAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_07_08" ON "SCHEMA_A"."T_MAIN" (
UPPER("COL_07"),
"COL_08"
) GLOBAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_02_16" ON "SCHEMA_A"."T_MAIN" (
"COL_02",
"COL_16"
) GLOBAL;
后续现场应急将表做了分区改造后,SQL 执行慢的现象消失。新表的表结构如下,按 COL_01 字段 hash 分区,该字段非业务 Where 条件字段,因此可以将访问通过 hash 的方式,打散到 4 个分区:
CREATE TABLE "T_MAIN" (
"COL_01" NUMBER(20) CONSTRAINT "T_MAIN_OBNOTNULL_1779764441392384" NOT NULL ENABLE,
"COL_02" NUMBER(20),
"COL_03" NUMBER(20),
"COL_04" VARCHAR2(3),
"COL_05" VARCHAR2(6),
"COL_06" VARCHAR2(64),
"COL_07" VARCHAR2(256),
"COL_08" VARCHAR2(512),
"COL_09" VARCHAR2(16),
"COL_10" VARCHAR2(8),
"COL_11" NUMBER(20),
"COL_12" VARCHAR2(256),
"COL_13" NUMBER(10),
"COL_14" VARCHAR2(256),
"COL_15" NUMBER(20),
"COL_16" VARCHAR2(3),
"COL_17" VARCHAR2(1024),
"COL_18" NUMBER(5),
"COL_19" NUMBER(20),
"COL_20" DATE,
"COL_21" VARCHAR2(1),
"COL_22" VARCHAR2(32),
"COL_23" VARCHAR2(32),
"COL_24" NUMBER(1),
"COL_25" VARCHAR2(50),
"COL_26" VARCHAR2(50),
"COL_27" VARCHAR2(50),
"COL_28" VARCHAR2(1),
"COL_29" DATE,
"COL_30" NUMBER(20),
"COL_31" NUMBER(20),
"COL_32" NUMBER(20),
"COL_33" VARCHAR2(256),
"COL_34" DATE,
"COL_35" VARCHAR2(32),
"COL_36" VARCHAR2(64),
"COL_37" NUMBER(20),
"COL_38" NUMBER(20),
CONSTRAINT "PK_T_MAIN" PRIMARY KEY ("COL_01"),
CONSTRAINT "UK_T_MAIN_CODE" UNIQUE ("COL_07")
) COMPRESS FOR ARCHIVE REPLICA_NUM = 3 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
PARTITION BY HASH(COL_01)
(PARTITION P_1,
PARTITION P_2,
PARTITION P_3,
PARTITION P_4);
CREATE INDEX "SCHEMA_A"."IDX_COL_07_08" ON "SCHEMA_A"."T_MAIN" (
UPPER("COL_07"),
"COL_08"
) LOCAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_02" ON "SCHEMA_A"."T_MAIN" (
"COL_02"
) LOCAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_13" ON "SCHEMA_A"."T_MAIN" (
"COL_13"
) LOCAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_14" ON "SCHEMA_A"."T_MAIN" (
"COL_14"
) LOCAL;
CREATE INDEX "SCHEMA_A"."IDX_COL_02_16" ON "SCHEMA_A"."T_MAIN" (
"COL_02",
"COL_16"
) GLOBAL;
关键诊断信息
触发条件
- 大量线程并发查询,可以通过 OCP 观察 TOP 执行 SQL。
- 入参高度集中,访问单行或极少量行(RETURN_ROWS 较小)。
- 存在索引回表。
- 热点行缓存数据的引用计数基于 CAS 维护,存在 CAS 原子竞争。
事后诊断
在问题环境上用问题 SQL 压测复现:以 40 并发的文本协议测试,使 SQL 耗时范围为 5~20ms,查看表 Leader 30 节点上的 CPU 升高到 24%。
对应 40 并发的 perf 火焰图信息,可以看到 SQL 耗时主要集中在 kvcache 下的原子操作(atomic)相关函数上;对照 60 并发的 perf 信息,这两个函数的占比明显上升。
CPU 耗时主要在原子操作相关函数上,这些函数通过 CAS(Compare-And-Swap,比较并交换)原理维护引用计数。CAS 是由 CPU 硬件指令直接支持的无锁同步机制:任意时刻只有一个线程的 CAS 操作能够成功,其余线程需要重新读取并重试。当 N 个线程同时查询同一行缓存记录时,只有一个线程能成功,其余 N-1 个线程都在空转自旋,线程越多,空转越严重:
| 并发线程数 | 成功工作的线程 | 空转的线程 | CPU 有效吞吐占比 |
|---|---|---|---|
| 10 | 1 | 9 | ~10% |
| 50 | 1 | 49 | ~2% |
| 100 | 1 | 99 | ~1% |
此外,在 x86 环境上测试后复现:合并并 flush kvcache 后并发压测,也出现了走 kvcache + CAS 竞争的路径。
压测结果显示,40 并发下,x86 环境(64C)原子操作函数耗时占比约 6.86%,SQL 耗时 4~7ms(空库压测);ARM 环境(192C)原子操作函数耗时占比约 14.59%,SQL 耗时 5~20ms。由此可见,该问题在 x86 和 ARM 两个架构下均有概率出现,ARM 环境下更易触发。
问题原因
集群从 x86 切换至 ARM 后,新主集群由主备切换形成,表数据稳定落在 SSTable 上,SQL 读取该表走 SSTable 单行读取路径。当业务以相同参数高频并发访问同一行(或极少量行)数据时,多个线程需要同时维护该行缓存数据的引用计数,而引用计数基于 CAS 无锁机制实现。高并发同一热点的场景下,CAS 重试导致大量线程 CPU 空转自旋,从而引发 CPU 打满的性能瓶颈。
为什么切换前 x86 上没有出现该问题:
- 在 x86 测试环境上可以复现该热点现象,说明 x86 场景下同样可能存在;同样 40 并发下,x86 环境 SQL 耗时更快,因此该问题在 x86 上较 ARM 更难触发。
- 从原理角度看,随着时间推移,表数据在缓存中的分布会越来越分散(存在 DML 时,访问并发会分散到不同版本的数据缓存中),日常访问走多版本扫描路径,原子操作集中度低,也在一定程度上缓解了热点现象。
为什么分区改造后问题消失:
慢 SQL 上作为查询条件的两个字段都不是分区键,分区改造后查询被 hash 打散到 4 个分区上,分摊了查询流量,缓解了热点访问。
问题的风险及影响
SQL 读表走 SSTable 单行读取路径,业务并发高频使用同一条件,随着并发增长,高并发下出现 CAS 原子竞争,导致大量执行相同 SQL 的线程 CPU 空转自旋,继而出现 CPU 被打满,最终恶化为大量相同 SQL 排队、长时间无法执行完毕的情况。
影响租户
| sys | MySQL | Oracle |
|---|---|---|
| YES | YES | YES |
影响版本
| 影响版本 | 修复版本 |
|---|---|
| OBServer 3.x | 4.3.5 及之后版本 |
| OBServer V4.2.x 及更早版本 | 4.3.5 及之后版本 |
解决方法
应用侧开发规范建议:
- 结果集缓存:建议业务侧检查是否存在入参一致的高频业务行为,并评估是否可以从业务层提前改造避免,或在前端利用结果缓存等类似机制,避免该场景下的压力直接释放给数据库侧。
应用数据层语句优化建议:
- 表分区改造:该场景问题通过分区表改造分散热点。注意不能通过 Where 条件字段分区,否则分区裁剪仍然会带来集中热点的问题,但这种方式本身可能带来访问效率的上升(访问多分区的额外开销与热点争用的权衡)。
- 覆盖索引:对于可以建立覆盖索引的场景(本例 SQL 几乎涉及表全列,不适合该方案;适合 select 列较少的场景),通过避免回表的方式减少对 kvcache 的访问频率。
数据库架构优化建议:
- 升级至 4.3.5 及之后版本。
规避方式
同解决方法。