首批通过分布式安全可靠测评,为关键业务系统打造
强读路由
更新时间:2026-04-10 16:11:03
本文介绍什么是强读路由,并结合示例介绍 ODP 强读路由选取的过程。
背景介绍
在 OceanBase 数据库中,分区(Partition)是数据存储的基本单元。当您创建表时,就会存在表和分区 (Partition) 的映射。如果创建的是非分区表,一张表仅对应一个分区 (Partition) ,如果创建的是分区表,一张表可能会对应多个分区 (Partition) 。
目前 OceanBase 数据库未实现分区 (Partition) 的合并和分裂。假设客户端请求某表的 P0 分区,P0 分区在 OBServer0 节点和 OBServer2 节点上,ODP 却将请求路由到 OBServer1 节点,因为 OBServer1 节点不存在该分区数据,所以 ODP 会将请求再次路由到 OBServer0 节点或 OBServer2 节点,从而产生远程计划(Remote Plan)。而 ODP 若保存着自 P0 分区到 OBServer0 节点和自 P0 分区到 OBServer2 节点的映射,将会保证将请求路由到存在该分区数据的 OBServer 节点,避免远程计划。
路由时仅路由到分区所在的 OBServer 节点还不够,还需要知道分区的主备信息,每个分区在 OceanBase 数据库中存在主副本(Leader)和备副本(Follower),他们分布在不同的 OBServer 节点上面,假设 OBServer0 节点为主副本、OBServer2 节点为备副本,此时一个强读请求被路由到了备副本,备副本会将请求路由至主副本,产生远程计划。ODP 可以区分强读/弱读请求,将强读请求发往主副本,避免远程计划。
说明
在 OceanBase 数据库中,有 Local 计划、Remote 计划和 Distributed 计划三种表路由。Local 计划、Remote 计划均为单分区的路由。ODP 的作用就是尽量消除 Remote 计划,将路由尽可能地变为 Local 计划。如果表路由类型为 Remote 计划的 SQL 过多,说明该 ODP 的路由可能存在问题(可通过查看 oceanbase.GV$OB_SQL_AUDIT 视图中 plan_type 字段来确认表路由类型)。表路由的详细介绍可参见 OceanBase 数据库文档 ODP 表路由 一文。
路由选取
客户端发送分区表强读请求 SQL,SQL 语句提供分区键值、表达式或分区名,ODP 解析出分区 ID,通过分区 ID 查找并将请求路由到对应 Leader 副本位置。本节结合几个示例介绍 ODP 对强读请求的路由过程。
SQL 语句中提供分区键列值
例如
T0表分区键为C1,查询语句可以为SELECT * FROM T0 WHERE C1 = xxxx;。具体路由选取过程可参见 示例一:SQL 语句中提供分区键值。SQL 语句中提供分区键列表达式
例如
T0表分区键为C1,查询语句可以为SELECT * FROM T0 WHERE C1 = ABS(xxxx);。具体路由选取过程可参见 示例二:SQL 语句中提供分区键表达式。SQL 语句中提供分区名称
例如
T0表一级分区名称有P0、P1、P2,二级分区名称有SP0、SP1、SP2,查询语句可以为INSERT INTO T0 PARTITION(P0SSP2) VALUES(xxxx);。具体路由选取过程可参见 示例三:SQL 语句中提供分区名称注意
指定分区名称的语法为
SELECT/UPDATE/INSERT ... table_name PARTITION(partition_name[Ssubpartition_name]),分区表的一级分区默认名称为 P0、P1、P2 等,二级分区默认名称为 SP0、SP1、SP2 等。使用指定分区名称路由时如果不指定二级分区名称,则删去语句中标识指定二级分区名的前缀字符
S。
Multi-Stmt 语句
ODP 自 V4.3.1.5(V4.3.1 系列)和 V4.3.5(V4.3.5 系列)起,对 Multi-Stmt 的路由计算进行优化:当第一条语句为事务开启时(
begin/start transaction),使用第二条语句作为路由计算依据;当第一条语句为非事务开启时,使用 Multi-Stmt 的第一条语句作为路由计算的依据。具体路由选取过程可参见 示例四:Multi-Stmt 语句。
示例一:SQL 语句中提供分区键值
登录 OceanBase 数据库,执行如下命令创建分区表。
obclient [test]> CREATE TABLE T0(C1 INT) PARTITION BY HASH(C1) PARTITIONS 8;为将要执行的 SQL 语句提供分区键值。
obclient [test]> SELECT /* +READ_CONSISTENCY(STRONG) */ * FROM T0 WHERE C1=123;使用 EXPLAIN ROUTE 命令查看 ODP 路由选取的过程。
obclient [test]> EXPLAIN ROUTE SELECT /* +READ_CONSISTENCY(STRONG) */ * FROM T0 WHERE C1=123\G输出如下,ODP 将 SQL 语句路由至分区主副本。
*************************** 1. row *************************** ... Route Plan ----------------- > SQL_PARSE:{cmd:"COM_QUERY", table:"T0"} > ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} > LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} > TABLE_ENTRY_LOOKUP_DONE:{table:"T0", table_id:500084, partition_num:8, table_type:"USER TABLE", entry_from_remote:false} > PARTITION_ID_CALC_START:{} > EXPR_PARSE:{col_val:"C1=123"} > RESOLVE_EXPR:{part_range:"[123 ; 123]"} > RESOLVE_TOKEN:{token_type:"TOKEN_INT_VAL", resolve:{"BIGINT":123}, token:"123"} > CALC_PARTITION_ID:{part_description:"partition by hash(INT(binary)) partitions 8"} > PARTITION_ID_CALC_DONE:{partition_id:200065, level:1, partitions:"(p3)"} > PARTITION_ENTRY_LOOKUP_DONE:{leader:"10.10.10.1:50109", entry_from_remote:false} > ROUTE_POLICY:{chosen_route_type:"ROUTE_TYPE_LEADER"} > CONGESTION_CONTROL:{svr_addr:"10.10.10.1:50109"}
示例二:SQL 语句中提供分区键表达式
登录 OceanBase 数据库,执行如下命令创建分区表。
obclient [test]> CREATE TABLE T0(C1 INT) PARTITION BY HASH(C1) PARTITIONS 8;为将要执行的 SQL 语句提供分区键表达式。
obclient [test]> SELECT /* +READ_CONSISTENCY(STRONG) */ * FROM T0 WHERE C1=ABS(123);使用 EXPLAIN ROUTE 命令查看 ODP 路由选取的过程。
obclient [test]> EXPLAIN ROUTE SELECT /* +READ_CONSISTENCY(STRONG) */ * FROM T0 WHERE C1=123\G输出如下,ODP 完成 ABS() 函数计算后,路由至分区主副本。
*************************** 1. row *************************** ... Route Plan ----------------- > SQL_PARSE:{cmd:"COM_QUERY", table:"T0"} > ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} > LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} > TABLE_ENTRY_LOOKUP_DONE:{table:"T0", table_id:500084, partition_num:8, table_type:"USER TABLE", entry_from_remote:false} > PARTITION_ID_CALC_START:{} > EXPR_PARSE:{col_val:"C1=123"} > RESOLVE_EXPR:{part_range:"[123 ; 123]"} > RESOLVE_TOKEN:{token_type:"TOKEN_INT_VAL", resolve:{"BIGINT":123}, token:"123"} > CALC_PARTITION_ID:{part_description:"partition by hash(INT(binary)) partitions 8"} > PARTITION_ID_CALC_DONE:{partition_id:200065, level:1, partitions:"(p3)"} > PARTITION_ENTRY_LOOKUP_DONE:{leader:"10.10.10.1:50109", entry_from_remote:false} > ROUTE_POLICY:{chosen_route_type:"ROUTE_TYPE_LEADER"} > CONGESTION_CONTROL:{svr_addr:"10.10.10.1:50109"}
示例三:SQL 语句中提供分区名称
登录 OceanBase 数据库,执行如下命令创建分区表。
obclient [test]> CREATE TABLE T0(C1 INT) PARTITION BY HASH(C1) PARTITIONS 8;创建分区表时未指定分区名称,将会使用默认分区名称。
为将要执行的 SQL 语句提供分区名称。
obclient [test]> SELECT /* +READ_CONSISTENCY(STRONG) */ * FROM T0 PARTITION(P1) WHERE C1=123;使用 EXPLAIN ROUTE 命令查看 ODP 路由选取的过程。
obclient [test]> EXPLAIN ROUTE SELECT * FROM T0 PARTITION(p1) WHERE C1=123\G输出如下,ODP 将 SQL 语句路由至 P0 分区。
Trans Current Query:"EXPLAIN ROUTE SELECT * FROM T0 PARTITION(p1) WHERE C1=123" Route Prompts ----------------- > ROUTE_INFO [INFO] Will route to partition server or routed by route policy > PARTITION_ID_CALC_DONE [INFO] Will route to specified partition name(p1) Route Plan ----------------- > SQL_PARSE:{cmd:"COM_QUERY", table:"T0"} > ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} > LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} > TABLE_ENTRY_LOOKUP_DONE:{table:"T0", table_id:500084, partition_num:8, table_type:"USER TABLE", entry_from_remote:false} > PARTITION_ID_CALC_DONE:{partition_id:200063, level:1, part_name:"p1"} > PARTITION_ENTRY_LOOKUP_DONE:{leader:"10.10.10.3:50111"} > ROUTE_POLICY:{chosen_route_type:"ROUTE_TYPE_LEADER"} > CONGESTION_CONTROL:{svr_addr:"10.10.10.3:50111"}Will route to specified partition name(p1)说明 ODP 将请求路由至语句中指定的分区名的分区。
示例四:Multi-Stmt 语句
登录 OceanBase 数据库,执行如下命令创建分区表
obclient [test]> CREATE TABLE T0(C1 INT, C2 VARCHAR(10)) PARTITION BY HASH(C1) PARTITIONS 8; obclient [test]> CREATE TABLE T1(C1 INT, C2 VARCHAR(10)) PARTITION BY HASH(C1) PARTITIONS 8;执行 Multi-Stmt
BEGIN; UPDATE T0 SET C2 = 'obproxy' WHERE C1=123; INSERT INTO T1 (C1, C2) VALUES (12, 'odp'); COMMIT;查看诊断日志
说明
需将 route_diagnosis_level 配置项修改为
4才可在obproxy_diagnosis.log日志中查看到对应路由信息,具体介绍可参见 获取诊断信息 一文。从诊断日志(
obproxy_diagnosis.log)中获取执行对应日志的时间戳(此处示例为2025-06-23 17:57:11.647321),执行如下命令将对应日志转化为树状诊断过程,方便查看。grep "2025-06-23 17:57:11.647321" obproxy_diagnosis.log | sed "s/\/n/\n/g"输出如下,可以看到事务开启的情况下,ODP 使用 Multi-Stmt 的第二条语句作为路由计算依据,将语句路由到了
t0表。[2025-07-21 14:35:55.606826] [7477][Y0-00007F27DFF20760] [ROUTE]([ROUTE](*route_diagnosis= Trans Current Query:"BEGIN;UPDATE T0 SET C2 = 'obproxy' WHERE C1=123;INSERT INTO T1 (C1, C2) VALUES (12, 'odp');COMMIT;" Route Prompts > ROUTE_INFO [INFO] Will do table partition location lookup to decide which OBServer to route to > ROUTE_POLICY [INFO] Will route to table's partition leader replica(10.10.10.1:2881) using route policy PRIMARY_ZONE_FIRST because query for STRONG read > CONGESTION_CONTROL [INFO] This replica(10.10.10.1:2881) is no need to pass congestion control Route Plan > SQL_PARSE:{cmd:"OB_MYSQL_COM_QUERY", table:"t0"} > ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} > LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} > TABLE_ENTRY_LOOKUP_START:{} > FETCH_TABLE_RELATED_DATA:{part_level:1, first part_expr:"c1"} > TABLE_ENTRY_LOOKUP_DONE:{table:"t0", table_id:"500038", table_type:"USER TABLE", partition_num:8} > PARTITION_ID_CALC_START:{} > EXPR_PARSE:{col_val:"C2=obproxy,C1=123"} > RESOLVE_TOKEN:{token_type:"TOKEN_STR_VAL", resolve:"VARCHAR:obproxy<utf8mb4_general_ci>", token:"obproxy"} > RESOLVE_TOKEN:{token_type:"TOKEN_INT_VAL", resolve:"BIGINT:123", token:"123"} > CALC_PARTITION_ID:{part_description:"partition by hash(INT<binary>) partitions 8"} > PARTITION_ID_CALC_DONE:{partition_id:200016, level:1, partitions:"(p3)", parse_sql:"UPDATE T0 SET C2 = 'obproxy' WHERE C1=123;"} > PARTITION_ENTRY_LOOKUP_DONE:{leader:"10.10.10.1:2881"} > ROUTE_POLICY:{route_policy:"", chosen_route_type:"ROUTE_TYPE_LEADER", type:"FULL"} > CONGESTION_CONTROL:{svr_addr:"10.10.10.1:2881", need_congestion_lookup:false} > HANDLE_RESPONSE:{is_parititon_hit:"true", send_action:"SERVER_SEND_REQUEST", state:"TRANSACTION_COMPLETE"} )
登录 OceanBase 数据库,执行如下命令创建两个分区表
t0、t1obclient [test]> CREATE TABLE t0(C1 INT, C2 VARCHAR(10)) PARTITION BY HASH(C1) PARTITIONS 8; obclient [test]> CREATE TABLE t1(C1 INT, C2 VARCHAR(10)) PARTITION BY HASH(C1) PARTITIONS 8;执行 Multi-Stmt
INSERT INTO t1 (C1, C2) VALUES (1, 'ob'), (2, 'odp'); UPDATE t0 SET C2 = 'odp' WHERE C1=123;查看诊断日志
说明
需将 route_diagnosis_level 配置项修改为
4才可在obproxy_diagnosis.log日志中查看到对应路由信息,具体介绍可参见 获取诊断信息 一文。从诊断日志(
obproxy_diagnosis.log)中获取执行对应日志的时间戳(此处示例为2025-06-24 14:07:02.793704),执行如下命令将对应日志转化为树状诊断过程,方便查看。grep "2025-06-24 14:07:02.793704" obproxy_diagnosis.log | sed "s/\/n/\n/g"输出如下,可以看到非事务开启的情况下,ODP 使用 Multi-Stmt 的第一条语句作为路由计算依据,将语句路由到了
t1表。[2025-06-24 14:07:02.793704] [10559][Y0-00007FDDF9C95760] [ROUTE]([ROUTE](*route_diagnosis= Trans Current Query:"INSERT INTO t1 (C1, C2) VALUES (1, 'ob'), (2, 'odp');UPDATE t0 SET C2 = 'odp' WHERE C1=123;" Route Prompts > ROUTE_INFO [INFO] Will do table partition location lookup to decide which OBServer to route to > ROUTE_POLICY [INFO] Will route to table's partition leader replica(10.10.10.1:4881) using route policy PRIMARY_ZONE_FIRST because query for STRONG read Route Plan > SQL_PARSE:{cmd:"OB_MYSQL_COM_QUERY", table:"t1"} > ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} > LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} > TABLE_ENTRY_LOOKUP_START:{} > FETCH_TABLE_RELATED_DATA:{part_level:1, first part_expr:"c1"} > TABLE_ENTRY_LOOKUP_DONE:{table:"t1", table_id:"500011", table_type:"USER TABLE", partition_num:8} > PARTITION_ID_CALC_START:{} > EXPR_PARSE:{col_val:"C1=1,C2=ob"} > RESOLVE_TOKEN:{token_type:"TOKEN_INT_VAL", resolve:"BIGINT:1", token:"1"} > RESOLVE_TOKEN:{token_type:"TOKEN_STR_VAL", resolve:"VARCHAR:ob<utf8mb4_general_ci>", token:"ob"} > CALC_PARTITION_ID:{part_description:"partition by hash(INT<binary>) partitions 8"} > PARTITION_ID_CALC_DONE:{partition_id:200010, level:1, partitions:"(p1)"} > PARTITION_ENTRY_LOOKUP_DONE:{leader:"10.10.10.1:4881"} > ROUTE_POLICY:{route_policy:"", chosen_route_type:"ROUTE_TYPE_LEADER", type:"FULL"} > CONGESTION_CONTROL:{svr_addr:"10.10.10.1:4881"} > HANDLE_RESPONSE:{is_parititon_hit:"true", send_action:"SERVER_SEND_REQUEST", state:"TRANSACTION_COMPLETE"} )