首批通过分布式安全可靠测评,为关键业务系统打造
如何判断 Batch DML 是否使用了 ArrayBinding 协议及 Batch 成组执行优化
更新时间:2026-05-28 02:06
为了减少客户端和数据库之间的 RPC 交互和执行上下文切换的开销,OceanBase 提供了 Batch DML 执行功能来优化开销。OceanBase 数据库 Oracle 模式下,支持了 ArrayBinding 协议,通过给一条语句绑定一组 Array 参数来达到批量执行的目的;OceanBase 数据库 MySQL 模式下,支持了 Multi Queries 协议,通过将参数改写成以;分割的多条语句,以一个 Multi Queries 协议包的形式发送给 OBServer,并依次执行 Multi Qeries 协议包中的语句,并返回对应的结果。 本文主要介绍在实际业务中如何判断 Batch DML 是否使用了 ArrayBinding 协议及 Batch 成组执行优化,进而评估 DML 的性能是否符合预期。
详细说明
使用 ArrayBinding 的先决条件
按照当前设计及 ArrayBinding 实现上的限制,使用 ArrayBinding 协议及 Batch 成组执行优化的先决条件有以下几个:
OceanBase 数据库 Oracle 模式租户。
租户级参数
ob_enable_batched_multi_statement需要打开(如果没有开启该配置项的话 OBServer 内核不会做 Batch 成组执行优化)。Plan Cache 需要打开,不能禁用。
OB-JDBC URL 中需要添加
useArrayBinding=true。业务代码中连接成功后(在执行
Batch DML之前)执行过connection.setAutoCommit(false)。业务代码中使用了
addBatch()和executeBatch()来尝试批量执行。ArrayBinding还需要开启 PS 二合一协议。
备注:OB-JDBC 连接串设置,满足以下两个任意条件都会自动开启 PS 二合一协议:
useServerPrepStmts=true&usePieceData=true。useOraclePrepareExecute=true(仅限 Oracle 租户)。、
如何判断 Batch DML 是否成功使用了 ArrayBinding 协议及 Batch 成组执行优化
方法一:检查对应的 SQL Audit 记录中的 query_sql 和 affected_rows 字段
测试 Batch Insert 是否走了 ArrayBinding 协议,测试代码如下:
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
public class TestOBOracleBatch {
public static void main(String[] args) {
try {
String driver = "com.oceanbase.jdbc.Driver";
String url = "jdbc:oceanbase://xx.xxx.80.111:2881/ALVIN?useArrayBinding=true&useOraclePrepareExecute=true";
String user = "ALVIN@oracle";
String password = "xxx";
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
connection.setAutoCommit(false);
/*
CREATE TABLE emp (
empno NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY,
ename VARCHAR2(10),
job VARCHAR2(9),
mgr NUMBER(4),
hiredate DATE,
sal NUMBER(7,2),
comm NUMBER(7,2),
deptno NUMBER(2)
);
create index emp_ename_idx on emp(ename);
*/
PreparedStatement pstmt = connection.prepareStatement("INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
// 批量设置参数
pstmt.setInt(1, 7369);
pstmt.setString(2, "SMITH");
pstmt.setString(3, "CLERK");
pstmt.setInt(4, 7902);
pstmt.setDate(5, java.sql.Date.valueOf("1980-12-17"));
pstmt.setDouble(6, 800.00);
pstmt.setDouble(7, 0.00);
pstmt.setInt(8, 20);
pstmt.addBatch();
pstmt.setInt(1, 7499);
pstmt.setString(2, "ALLEN");
pstmt.setString(3, "SALESMAN");
pstmt.setInt(4, 7698);
pstmt.setDate(5, java.sql.Date.valueOf("1981-02-20"));
pstmt.setDouble(6, 1600.00);
pstmt.setDouble(7, 300.00);
pstmt.setInt(8, 30);
pstmt.addBatch();
pstmt.setInt(1, 7521);
pstmt.setString(2, "WARD");
pstmt.setString(3, "SALESMAN");
pstmt.setInt(4, 7698);
pstmt.setDate(5, java.sql.Date.valueOf("1981-02-22"));
pstmt.setDouble(6, 1250.00);
pstmt.setDouble(7, 500.00);
pstmt.setInt(8, 30);
pstmt.addBatch();
// 执行批量操作
pstmt.executeBatch();
System.out.println("Succ");
connection.commit();
pstmt.close();
connection.close();
}
catch (Throwable e) {
e.printStackTrace();
}
}
}
编译执行如下:
[admin@observer1 /home/admin]$ javac -cp ./oceanbase-client-2.4.14.1.jar TestOBOracleBatchInsert.java
[admin@observer1 /home/admin]$ java -cp ./oceanbase-client-2.4.14.1.jar:./ TestOBOracleBatchInsert
Batch insert succeeded !
检查对应的 SQL Audit 执行记录:
MySQL [oceanbase]> select usec_to_time(request_time),plan_id,is_hit_plan,trace_id,query_sql,params_value,affected_rows,is_batched_multi_stmt,PS_CLIENT_STMT_ID,ps_inner_stmt_id from gv$ob_sql_audit where tenant_id=1004 and db_name='alvin' order by request_time\G
...
*************************** 8. row ***************************
usec_to_time(request_time): 2025-07-23 15:39:49.147114
plan_id: 0
is_hit_plan: 0
trace_id: YB420BA6506F-000639F24CDECF80-0-0
query_sql: set autocommit = 0
params_value:
affected_rows: 0
is_batched_multi_stmt: 0
PS_CLIENT_STMT_ID: -1
ps_inner_stmt_id: -1
*************************** 9. row ***************************
usec_to_time(request_time): 2025-07-23 15:39:49.180474
plan_id: 4021
is_hit_plan: 0
trace_id: YB420BA6506F-000639F24CDECF81-0-0
query_sql: INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
params_value: NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL
affected_rows: 3
is_batched_multi_stmt: 1
PS_CLIENT_STMT_ID: 1
ps_inner_stmt_id: -1
*************************** 10. row ***************************
usec_to_time(request_time): 2025-07-23 15:39:49.207106
plan_id: 0
is_hit_plan: 0
trace_id: YB420BA6506F-000639F24CDECF82-0-0
query_sql: COMMIT
params_value:
affected_rows: 0
is_batched_multi_stmt: 0
PS_CLIENT_STMT_ID: -1
ps_inner_stmt_id: -1
10 rows in set (0.06 sec)
可以发现:这边 query_sql: INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES (?, ?, ?, ?, ?, ?, ?, ?) 只包含了一对(),即一条记录,但是 affected_rows: 3 表明实际插入了 3 条记录,说明该 SQL 走了 ArrayBinding 协议和 Batch 成组执行优化。
方法二:配合方法一检查 SQL Audit 和 Plan Cache 中的 is_batched_multi_stmt 字段
配合方法一,可以进一步检查 SQL Audit 记录和 Plan Cache 中的 is_batched_multi_stmt 字段是否为 1,如果为 1,则表明走到了 Batch 成组执行优化:
MySQL [oceanbase]> select * from gv$ob_plan_cache_plan_stat where plan_id=4021\G
*************************** 1. row ***************************
TENANT_ID: 1004
SVR_IP: xx.xxx.80.111
SVR_PORT: 2882
PLAN_ID: 4021
SQL_ID: D0DC764705DA5AC12F3E22F7AFE730F0
TYPE: 1
IS_BIND_SENSITIVE: 0
IS_BIND_AWARE: 0
DB_ID: 500192
STATEMENT: INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
QUERY_SQL: INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
SPECIAL_PARAMS:
PARAM_INFOS: {1,0,0,-1,0},{1,0,0,-1,0},{1,0,0,-1,0},{1,0,0,-1,0},{1,0,0,-1,0},{1,0,0,-1,0},{1,0,0,-1,0},{1,0,0,-1,0}
SYS_VARS: 45,45,2151677954,+08:00,2,4,1,0,0,3,1,0,1,10485760,1,0,YYYY-MM-DD HH24:MI:SS,YYYY-MM-DD HH24:MI:SS.FF9,YYYY-MM-DD HH24:MI:SS.FF TZR TZD,BINARY,BINARY,AL32UTF8,AL16UTF16,BYTE,FALSE,1,100,64,200,0,13,NULL,1,1,1,1,1,0,0,0,1000,BLOOM_FILTER,RANGE,IN,1,17180001539,17180001539,1,45,0,0,
CONFIGS: 3,1,1,0,1,1,0,0,30,17180001540,10,0,0,0,1,
PLAN_HASH: 3290318591324336132
FIRST_LOAD_TIME: 2025-07-23 15:39:49.198406
SCHEMA_VERSION: 1753256363021920
LAST_ACTIVE_TIME: 2025-07-23 15:39:49.199940
AVG_EXE_USEC: 19719
SLOWEST_EXE_TIME: 2025-07-23 15:39:49.199940
SLOWEST_EXE_USEC: 19719
SLOW_COUNT: 0
HIT_COUNT: 0
PLAN_SIZE: 78064
EXECUTIONS: 1
DISK_READS: 0
DIRECT_WRITES: 0
BUFFER_GETS: 0
APPLICATION_WAIT_TIME: 0
CONCURRENCY_WAIT_TIME: 0
USER_IO_WAIT_TIME: 0
ROWS_PROCESSED: 3
ELAPSED_TIME: 19719
CPU_TIME: 19692
LARGE_QUERYS: 0
DELAYED_LARGE_QUERYS: 0
DELAYED_PX_QUERYS: 0
OUTLINE_VERSION: 0
OUTLINE_ID: -1
OUTLINE_DATA: /*+BEGIN_OUTLINE_DATA USE_DISTRIBUTED_DML(@"INS$1") OPTIMIZER_FEATURES_ENABLE('4.2.5.3') END_OUTLINE_DATA*/
ACS_SEL_INFO:
TABLE_SCAN: 0
EVOLUTION: 0
EVO_EXECUTIONS: 0
EVO_CPU_TIME: 0
TIMEOUT_COUNT: 0
PS_STMT_ID: 24
SESSID: 0
TEMP_TABLES:
IS_USE_JIT: 0
OBJECT_TYPE: SQL_PLAN
HINTS_INFO: /*+ */
HINTS_ALL_WORKED: 1
PL_SCHEMA_ID: 0
IS_BATCHED_MULTI_STMT: 1
RULE_NAME:
PLAN_STATUS: INACTIVE
ADAPTIVE_FEEDBACK_TIMES: NULL
FIRST_GET_PLAN_TIME: 14732
FIRST_EXE_USEC: 1704
1 row in set (0.20 sec)
从 gv$ob_sql_audit 和 gv$ob_plan_cache_plan_stat 中,都可以看到 IS_BATCHED_MULTI_STMT: 1,说明这边还同时使用上了 Batch 成组执行优化。
方法三:检查 observer.log(需要将 syslog_level 调高到 TRACE 级别及以上)
使用对应的 SQL 的 trace_id 去搜索对应执行节点的 observer.log:
[admin@observer1 /home/admin/oceanbase/log]$ grep Yxxxxx-xxxxxx-0-0 observer.log.20250723153* | grep -i array | grep -i bind
observer.log.20250723153951878:[2025-07-23 15:39:49.200232] TRACE [SERVER] try_batch_multi_stmt_optimization (obmp_stmt_execute.cpp:1817) [71213][T1004_L0_G0][T1004][Yxxxxx-xxxxxx-0-0] [lt=13] after try batched multi-stmt optimization(ret=0, stmt_type_=2, use_plan_cache=true, optimization_done=true, enable_batch_opt=true, is_ab_returning=false, THIS_WORKER.need_retry()=false, arraybinding_size_=3)
从 observer.log 日志中的关键字也可以看出,这边 Batch Insert 执行成功使用上了 ArrayBinding 协议和 Batch 成组执行优化:
try_batch_multi_stmt_optimization
after try batched multi-stmt optimization
arraybinding_size_=3
备注
目前 gv$ob_sql_audit 视图中的 is_batched_multi_stmt 字段有个已知的问题:即使 Batch DML 使用了 ArrayBinding 协议及 Batch 成组执行优化,这个 is_batched_multi_stmt 字段还是为 0,该问题修复在 OceanBase 数据库 V4.2.1 BP9(oceanbase-4.2.1.9-109000092024091919)、V4.2.5 BP4(oceanbase-4.2.5.4-104000082025052817)、V4.3.5 BP2(oceanbase-4.3.5.2-102000162025051417)版本。gv$ob_plan_cache_plan_stat 中的 is_batched_multi_stmt 则一直是准确的,可以优先参考使用后者。
适用版本
OceanBase 数据库 V4.x 版本。