×
技术社区 >  技术博客 >  自增主键与 OceanBase 旁路导入冲突详解

自增主键与 OceanBase 旁路导入冲突详解

1、OceanBase 旁路导入机制概述

  • **旁路导入(Direct Load)**是 OceanBase 提供的一种数据导入方式,其核心思想是绕过常规 SQL 引擎的行级写入与事务日志开销,直接将数据以批量方式写入存储层,从而在大规模导数场景下显著提升吞吐。

  • 在 OceanBase V4.3.5.x 中,当通过LOAD DATA INFILE 、INSERT INTO ... SELECT或 CREATE TABLE AS SELECT (CTAS)等语句导入数据且未显式指定旁路导入相关 Hint 时,数据的默认导入方式由租户级配置项目 default_load_mode 决定。

该参数的可选值包括:

  • DISABLED:不使用旁路导入(默认值),走常规导入路径;

  • FULL_DIRECT_WRITE:全量旁路导入,采用 insert 语义;

  • INC_DIRECT_WRITE:增量旁路导入,采用 insert 语义;

  • INC_REPLACE_DIRECT_WRITE:增量旁路导入的 INC_REPLACE 模式。

  • 当语句显示携带 APPEND / DIRECT / NO_DIRECT Hint 时,导入方式由 Hint 覆盖参数配置。

  • 此外,参数 direct_load_allow_fallback 控制在旁路导入遇到不支持的场景时,是否回退(Fallback)为普通导入:取值为 true 时静默降级,取值为 false 时直接报错。

旁路导入依赖并行 DML(Parallel DML,PDML)以实现高吞吐。然而并行写入与某些表结构特征存在语义冲突——最典型者即自增列(Auto Increment)主键:并行多线程写入无法在不引入全局协调开销的前提下保证自增值的全局唯一与有序,因此优化器在检测到目标表主键或分区键包含自增列时会强制禁用 PDML,进而使旁路导入不可用。

2、其他数据库批量导入对比

数据库 高性能导入机制 典型语句 与自增/序列的交互
OceanBase 旁路导入(Direct Load) INSERT ... SELECT(旁路)、LOAD DATA 自增列主键导致 PDML 禁用,旁路导入不可用
PostgreSQL COPY命令 COPY tbl FROM ... 序列(Sequence)由 nextval 保证唯一,导入通常串行推进
MySQL LOAD DATA INFILE LOAD DATA INFILE '...' INTO TABLE tbl 自增列可留空由引擎分配,单线程写入为主
Oracle 直接路径加载(Direct-Path Insert) INSERT /*+ APPEND */ 、SQL*Loader Direct Path 序列可用,APPEND 绕过缓冲区缓存直写数据块

各系统的"高性能导入"均以绕过或简化常规写入路径为核心手段;差异在于对约束(如唯一自增/序列)的处理策略。

OceanBase 的旁路导入在含自增列主键时选择"禁用并行 + 依赖回退"的保守策略,以保证数据正确性。

3、具体过程

3.1 围绕问题

  • RQ1(可执行性):在目标表主键包含自增列的前提下,default_load_mode与 direct_load_allow_fallback 的不同组合如何影响跨库INSERT … SELECT 语句的可执行性?何种组合会触发 PDML is disabled, direct load is not supported 错误?

  • RQ2(降级行为):当旁路导入不可用时,系统在何种条件下会自动降级(Fallback)为串行普通导入?降级行为在执行计划中如何体现?

  • RQ3(规避方案):对于报错且无法降级的场景,存在哪些有效的规避方案,其代价与适用性如何?

3.2 实验环境

配置项 取值
集群版本 OceanBase V4.3.5.5
租户规格 4C16G
租户模式 MySQL 模式
租户模板 OLAP
服务端口 2882
Zone 拓扑 多 Zone(cn-AAABBB-h-z0、cn-AAABBB-i-z0、cn-AAABBB-i-z1)
涉及 OBServer 10.XXX.0.1、10.XXX.0.2
客户端 obclient

3.3 数据集

create database db1;
use db1;

CREATE TABLE `report` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `date` date NOT NULL COMMENT '日期(天)',
  PRIMARY KEY (`id`),
  KEY `idx_date_id` (`date`, `id`) BLOCK_SIZE 16384 LOCAL
);

create database db2;
use db2;

CREATE TABLE `report2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `date` date NOT NULL COMMENT '日期(天)',
  PRIMARY KEY (`id`),
  KEY `idx_date_id` (`date`, `id`) BLOCK_SIZE 16384 LOCAL
) WITH COLUMN GROUP(all columns, each column);

表创建数据核验

obclient(obtt@(none))[db2]> select t1.table_id,t1.table_name,t2.database_name from oceanbase.__all_table t1 join oceanbase.__all_database t2 where t1.database_id=t2.database_id and table_name like 'report%' limit 10;
+----------+---------------------+-----------------+
| table_id | table_name          | database_name   |
+----------+---------------------+-----------------+
|  9570405 | report              | db1             |
|  9570408 | report2             | db2             |
+----------+---------------------+-----------------+
2 rows in set (0.030 sec)

插入表数据

  • 在库 1  db1 下创建存储过程 insert_report_data 为表 report 插入 1000 条记录;
use db1;

DELIMITER $$

CREATE PROCEDURE insert_report_data()
BEGIN

    DECLARE i INT DEFAULT 0;
    DECLARE total_rows INT DEFAULT 1000;
    DECLARE start_date DATE;
    

    SET start_date = '2023-01-01';
    

    START TRANSACTION;
    
    WHILE i < total_rows DO

        INSERT INTO report (`id`, `date`) 
        VALUES (NULL, DATE_ADD(start_date, INTERVAL i DAY));
        
        SET i = i + 1;
    END WHILE;
    

    COMMIT;

    SELECT CONCAT('Successfully inserted ', total_rows, ' rows into report table.') AS result_message;
END$$

DELIMITER ;

set ob_query_timeout=60000000;

call insert_report_data;
select count(1) from report;
+----------+
| count(1) |
+----------+
|     1000 |
+----------+
1 row in set (0.041 sec)

3.4 实验结果与分析

default_load_mode 基线查询:

default_load_mode **:**用于在导数场景中控制数据的导入方式。当通过 LOAD DATA INFILEINSERT INTO ... SELECTCREATE TABLE AS SELECT 语句导入数据且未指定旁路导入相关 Hint 时,导入方式由该配置项控制;若指定了 APPEND / DIRECT / NO_DIRECT Hint,则由 Hint 控制。

obclient(obtt@(none))[db1]SHOW PARAMETERS LIKE 'default_load_mode';
+------------------+----------+---------------+----------+-------------------+-----------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+--------+---------+-------------------+---------------+-----------+
| zone             | svr_type | svr_ip        | svr_port | name              | data_type | value    | info                                                                                                                                                                                                                                                                                                                                                                               | section | scope  | source  | edit_level        | default_value | isdefault |
+------------------+----------+---------------+----------+-------------------+-----------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+--------+---------+-------------------+---------------+-----------+
| cn-AAABBB-h-z0 | observer | 10.XXX.0.1    |     2882 | default_load_mode | STRING    | DISABLED | Specifies default load data path."DISABLED" represent load data not in direct load path (default value)."FULL_DIRECT_WRITE" represent load data in full direct load path with insert semantics."INC_DIRECT_WRITE" represent load data in inc direct load path with insert semantics."INC_REPLACE_DIRECT_WRITE" represent load data in inc direct load path with replace semantics. | TENANT  | TENANT | DEFAULT | DYNAMIC_EFFECTIVE | DISABLED      |         1 |
| cn-AAABBB-i-z0 | observer | 10.XXX.0.2    |     2882 | default_load_mode | STRING    | DISABLED | Specifies default load data path."DISABLED" represent load data not in direct load path (default value)."FULL_DIRECT_WRITE" represent load data in full direct load path with insert semantics."INC_DIRECT_WRITE" represent load data in inc direct load path with insert semantics."INC_REPLACE_DIRECT_WRITE" represent load data in inc direct load path with replace semantics. | TENANT  | TENANT | DEFAULT | DYNAMIC_EFFECTIVE | DISABLED      |         1 |
+------------------+----------+---------------+----------+-------------------+-----------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+--------+---------+-------------------+---------------+-----------+
2 rows in set (0.023 sec)

DISABLED 为该参数默认值,表示不走旁路导入路径;

edit_level = DYNAMIC_EFFECTIVE 表示参数修改动态生效,无需重启。

direct_load_allow_fallback 基线查询:

direct_load_allow_fallback:用于控制在旁路导入操作遇到不支持的场景时,导数操作是否回退为普通导入方式。

obclient(obtt@(none))[db1]SHOW PARAMETERS LIKE 'direct_load_allow_fallback';
+------------------+----------+---------------+----------+----------------------------+-----------+-------+--------------------------------------------------------------------------------------------------------------+---------+--------+---------+-------------------+---------------+-----------+
| zone             | svr_type | svr_ip        | svr_port | name                       | data_type | value | info                                                                                                         | section | scope  | source  | edit_level        | default_value | isdefault |
+------------------+----------+---------------+----------+----------------------------+-----------+-------+--------------------------------------------------------------------------------------------------------------+---------+--------+---------+-------------------+---------------+-----------+
| cn-AAABBB-h-z0 | observer | 10.XXX.0.1    | 2882     | direct_load_allow_fallback | BOOL      | False | Control whether an error is reported when direct load of the derivative operation scenario is not supported. | TENANT  | TENANT | DEFAULT | DYNAMIC_EFFECTIVE | True          | 0         |
| cn-AAABBB-i-z0 | observer | 10.XXX.0.2    | 2882     | direct_load_allow_fallback | BOOL      | False | Control whether an error is reported when direct load of the derivative operation scenario is not supported. | TENANT  | TENANT | DEFAULT | DYNAMIC_EFFECTIVE | True          | 0         |
+------------------+----------+---------------+----------+----------------------------+-----------+-------+--------------------------------------------------------------------------------------------------------------+---------+--------+---------+-------------------+---------------+-----------+
2 rows in set (0.022 sec)

该参数系统默认值为 True,但本集群当前值被设为 Falseisdefault = 0),意味着若不做调整,旁路导入不支持的场景将直接报错而非回退。

3.4.1 场景一:DISABLED & False(可执行,自动降级为串行插入)

Zone SVR_IP default_load_mode direct_load_allow_fallback
cn-AAABBB-i-z0 10.XXX.0.1 DISABLED False
cn-AAABBB-i-z1 10.XXX.0.2 DISABLED False

执行计划相关信息(EXPLAIN EXTENDED):

EXPLAIN EXTENDED
INSERT INTO report2 SELECT * FROM db1.report WHERE date < '2025-03-01'\G;
【输出(节选)】
Plan Type: DISTRIBUTED
Note:
 PDML disabled because the insert statement primary key or partition key has specified auto-increment column
 Degree of Parallelisim is 1 because of Auto DOP
Expr Constraints:
 range_placement('2025-03-01', MYSQL_DATE(10, 0)) = 1 result is TRUE
63 rows in set (0.054 sec)

执行结果:

INSERT INTO report2 SELECT * FROM db1.report WHERE date  SELECT COUNT(1) FROM report2;
+----------+
| count(1) |
+----------+
|      790 |
+----------+
1 row in set (0.015 sec)

由于 default_load_mode = DISABLED 本身就不走旁路导入,语句直接以普通导入方式执行;执行计划显示 PDML 因自增列被禁用、并行度降级为 1(串行)。语句成功导入 790 行,耗时 0.044 秒。

3.4.2 场景二:DISABLED & True(可执行,自动降级为串行插入)

Zone SVR_IP default_load_mode direct_load_allow_fallback
cn-AAABBB-i-z1 10.XXX.0.1 DISABLED True
cn-AAABBB-i-z0 10.XXX.0.2 DISABLED True

执行计划关键信息:

EXPLAIN EXTENDED
INSERT INTO report2 SELECT * FROM db1.report WHERE date < '2025-03-01'\G
【输出(节选)】
Plan Type: DISTRIBUTED
Note:
 PDML disabled because the insert statement primary key or partition key has specified auto-increment column
 Degree of Parallelisim is 1 because of Auto DOP
Expr Constraints:
 range_placement('2025-03-01', MYSQL_DATE(10, 0)) = 1 result is TRUE
63 rows in set (0.024 sec)

执行结果:

INSERT INTO report2 SELECT * FROM db1.report WHERE date SELECT COUNT(1) FROM report2;
+----------+
| count(1) |
+----------+
| 790      |
+----------+
1 row in set (0.017 sec)

default_load_mode = DISABLED 时导入本就不经旁路路径,因此无论 direct_load_allow_fallback 为 True 还是 False,语句均正常执行。执行计划与场景一一致(PDML 禁用、DOP=1)。成功导入 790 行,耗时 0.051 秒。

3.4.3 场景三:FULL_DIRECT_WRITE & False(报错,无法降级)

Zone SVR_IP default_load_mode direct_load_allow_fallback
cn-AAABBB-i-z1 10.XXX.0.1 FULL_DIRECT_WRITE False
cn-AAABBB-i-z0 10.XXX.0.2 FULL_DIRECT_WRITE False

执行计划关键信息:

-- 直接 EXPLAIN 旁路导入即报错,无法生成计划
EXPLAIN EXTENDED
INSERT INTO report2 SELECT * FROM db1.report WHERE date < '2025-03-01'\G;
ERROR 1235 (0A000): PDML is disabled, direct load is not supported
-- 使用 no_direct Hint 强制走普通导入路径后,可正常生成执行计划
EXPLAIN EXTENDED
INSERT /*+ no_direct */ INTO report2 SELECT * FROM db1.report WHERE date < '2025-03-01'\G;
【输出(节选)】
Plan Type: DISTRIBUTED
Note:
 PDML disabled because the insert statement primary key or partition key has specified auto-increment column
 Degree of Parallelisim is 1 because of Auto DOP
Expr Constraints:
 range_placement('2025-03-01', MYSQL_DATE(10, 0)) = 1 result is TRUE
65 rows in set (0.014 sec)

执行结果(直接执行旁路导入,报错):

INSERT INTO report2 SELECT * FROM db1.report WHERE date  < '2025-03-01';
【输出】
ERROR 1235 (0A000): PDML is disabled, direct load is not supported

 default_load_mode = FULL_DIRECT_WRITE 要求走旁路导入,但目标表含自增列主键导致 PDML 被禁用、旁路导入不被支持;同时 direct_load_allow_fallback = False 禁止回退,因此语句直接报错 PDML is disabled, direct load is not supported,无法完成导入。

解决方案:

-- 方案一:允许回退为普通导入(修改租户参数)
ALTER SYSTEM SET direct_load_allow_fallback = True;

-- 方案二:语句级显式指定 no_direct Hint,强制走普通导入路径(无需改参数)
INSERT /*+ no_direct */ INTO report2 SELECT * FROM `db1`.report WHERE date < '2025-03-01'; 

方案一为租户级全局修改,影响所有后续导数语句;

方案二为语句级临时规避,作用域最小、无副作用,推荐在无法/不宜修改租户参数时使用。

3.4.4 场景四:FULL_DIRECT_WRITE & True(可执行,自动降级为普通导入)

Zone SVR_IP default_load_mode direct_load_allow_fallback
cn-AAABBB-i-z1 10.XXX.0.1 FULL_DIRECT_WRITE True
cn-AAABBB-i-z0 10.XXX.0.2 FULL_DIRECT_WRITE True

执行计划关键信息:

EXPLAIN EXTENDED
INSERT INTO report2 SELECT * FROM `db1`.report WHERE date < '2025-03-01'\G;

【输出(节选)】
Plan Type: DISTRIBUTED
Note:
    PDML disabled because the insert statement primary key or partition key has specified auto-increment column
    Degree of Parallelisim is 1 because of Auto DOP
Expr Constraints:
    range_placement('2025-03-01', MYSQL_DATE(10, 0)) = 1 result is TRUE
63 rows in set (0.021 sec)

执行结果:

INSERT INTO report2 SELECT * FROM `db1`.report WHERE date < '2025-03-01'\G;
【输出】
Query OK, 790 rows affected (0.035 sec)
Records: 790  Duplicates: 0  Warnings: 0

在 direct_load_allow_fallback = True 的前提下,即便 default_load_mode = FULL_DIRECT_WRITE,由于回退被允许,旁路导入在遇到自增列约束时会自动降级为普通导入,从而避免报错。

4、最佳实践

4.1 跨库 INSERT … SELECT 导数参数(目标表主键含自增列)

场景 default_load_mode direct_load_allow_fallback 是否报错 降级行为 导入行数 执行时间 推荐操作
DISABLED False 降级为串行普通导入 790 0.044 s 可直接使用
DISABLED True 降级为串行普通导入 790 0.051 s 可直接使用
FULL_DIRECT_WRITE False 无法降级,报错 0(失败) 改用 no_direct Hint 或置 fallback=True
FULL_DIRECT_WRITE True 降级为串行普通导入 790 0.035 s 可直接使用

当且仅当 **default_load_mode = FULL_DIRECT_WRITE**(或其他旁路导入模式)且 **direct_load_allow_fallback = False** 且目标表主键含自增列时,语句报错且无法执行;其余组合均可成功执行(自动降级为串行普通导入)

4.2 分场景推荐方案

方案 A:调整租户参数 direct_load_allow_fallback = True

ALTER SYSTEM SET direct_load_allow_fallback = True;
  • **优点:**一次配置全局生效,后续所有导数语句自动降级,无需逐条改写 SQL。

  • **缺点:**全局副作用;掩盖了"旁路导入未生效"的事实,可能使DBA误以为正在使用旁路加速。

方案 B:语句级 no_direct Hint

INSERT /*+ no_direct */ INTO report2 SELECT * FROM db1.report WHERE date  < '2025-03-01';
  • **优点:**作用域最小,无全局副作用。

  • **缺点:**需逐条改写 SQL。

方案 C:改造表结构,去除自增列主键

  • **优点:**从根因上解除 PDML 禁用,使旁路导入得以真正生效并加速。

  • **缺点:**仅在追求旁路导入吞吐且业务允许时考虑。

精选推荐