首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >OceanBase 的 Offline DDL 是如何执行的?

OceanBase 的 Offline DDL 是如何执行的?

作者头像
爱可生开源社区
发布2025-09-04 10:18:51
发布2025-09-04 10:18:51
9300
代码可运行
举报
运行总次数:0
代码可运行

1. 来自客户的提问

  1. OceanBase 执行 Offline DDL 会加表锁吗?
  2. 执行 Offline DDL 的耗时如何评估?

在回答之前,先了解一下 OceanBase 的 Offline DDL 实现原理。

1.1 OceanBase 的 Offline DDL

OceanBase Offline DDL 操作是采用 “重建表” 的方式实现的。

具体而言,Offline DDL 操作会新建一张临时的隐藏表(对用户不可见),然后将原表的数据迁移到新建的表中。完成数据迁移后,临时表会被重命名为原表的名称,并且旧表将被删除。因此,Offline DDL 操作完成后,table_id 会发生变化。

注意,在 Offline DDL 执行期间不允许 DML 操作。

1.2 本文探究

  1. 对一个有 30 万行数据的表,执行更改列类型的操作(Offline DDL)。
    • 待更改的列上无索引,DDL 执行过程中不施加活跃事务(系统内部事务除外)。
  2. 观察 gv$ob_locks 视图解读表锁和对应步骤。
  3. 观察 gv$session_longops 视图解读 DDL 操作的执行状态和进度。
  4. 解读 DDL 过程的 OBServer 日志。
  5. 基于以上步骤,为 OceanBase 的 Offline DDL 操作影响和耗时评估提供一些思路。

2. DDL 操作

2.1 环境信息

  • OceanBase 版本:v4.2.1.10
  • 租户类型:MySQL

2.2 DDL 过程(视图观察)

初始表结构。

代码语言:javascript
代码运行次数:0
运行
复制
obclient [evan_db]> show create table t10;

| Table | Create Table                                                     
| t10   | CREATE TABLE `t10` (
`a` varchar(12) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
UNIQUE KEY `c_idx` (`c`) BLOCK_SIZE 16384 LOCAL,
KEY `idx_b` (`b`) BLOCK_SIZE 16384 LOCAL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |


1 row in set (0.014 sec)

查看表的 table_id

代码语言:javascript
代码运行次数:0
运行
复制
obclient [evan_db]> select distinct(table_id) from oceanbase.dba_ob_table_locations where table_name = 't10';
+----------+
| table_id |
+----------+
|   500037 |
+----------+
1 row in set (0.018 sec)

执行 DDL 操作(更改列类型),耗时 9.707s。

代码语言:javascript
代码运行次数:0
运行
复制
obclient [evan_db]> select count(*) from t10; selectnow();ALTER TABLE t10 MODIFY COLUMN a int(11);select now();
+----------+
| count(*) |
+----------+
|   300000 |
+----------+
1 row in set (0.030 sec)

+---------------------+
| now()               |
+---------------------+
| 2025-08-0517:24:16 |
+---------------------+
1 row in set (0.006 sec)

Query OK, 0 rows affected (9.707 sec)

+---------------------+
| now()               |
+---------------------+
| 2025-08-0517:24:26 |
+---------------------+
1 row in set (0.001 sec)

下面,我将解读 DDL 执行过程中产生的表锁及对应步骤的含义。

2.2.1 DDL 表锁解读

执行的 sql 内容(脚本执行,间隔 10ms 执行一次)。

代码语言:javascript
代码运行次数:0
运行
复制
SELECT 
    l.TYPE,
    l.ID1,
    l.ID2,
    l.LMODE,
    l.REQUEST,
    l.CTIME,
    l.BLOCK,
    t.trace_id,
    t.query_sql
FROM
    oceanbase.gv$ob_sql_audit t,
    oceanbase.gv$ob_locks l
WHERE
    t.tx_id = l.trans_id
    AND l.tenant_id = (
        SELECT tenant_id 
        FROM dba_ob_tenants 
        WHERE tenant_name = 'mysql_tenant'
    )
    AND l.type = 'TM';
    

步骤概览

  1. 锁定 tablet_id=200014table_id=500037,开启事务。
  2. 检查 t10(500037)依赖,确保 DDL 语句无冲突。
  3. 创建隐藏表 (table_id 500040),复制 t10 结构(含索引)。
  4. 定义隐藏表列,应用列 a 类型变更(int(11))。
  5. 记录 DDL 操作(创建、修改、同步)。
  6. 更新 t10 元数据,关联隐藏表 500040。
  7. 验证 schema 一致性。
  8. 同步全局 DDL 状态。
  9. 完成操作,table_id 切换到 500040,锁释放。

备注:

  1. gv$ob_locks 视图,锁类型(type)含义:TM 表示表锁,TX 表示事务锁,TR 表示行锁。
  2. 下文均为表锁捕捉到的步骤,篇幅所限,表锁类型及结果无关紧要的部分省略。
unsetunset步骤 1:事务开启(START TRANSACTION)unsetunset
  • 作用:针对 tablet_id=200014table_id=500037 施加表锁,保护元数据和数据分片。
  • 系统状态: DDL 操作启动,进入元数据变更准备。
代码语言:javascript
代码运行次数:0
运行
复制
[2025-08-05 17:24:16.378] TYPE  ID1 ID2 LMODE   REQUEST CTIME   BlOCK   trace_id    query_sql
TM  200014  NULL    X   NONE    26502   0   YB420ABA403E-000638C4C465F278-0-0   START TRANSACTION
TM  500037  NULL    X   NONE    28878   0   YB420ABA403E-000638C4C465F278-0-0   START TRANSACTION
unsetunset步骤 2:检查表依赖(SELECT __all_tenant_dependency)unsetunset
  • 作用:验证 ALTER 操作(修改列 a 类型)不会破坏 t10 的依赖关系(如索引依赖列 a)。独占锁保护依赖表查询。
  • 系统状态:确认 t10 无依赖冲突,准备创建隐藏表。
代码语言:javascript
代码运行次数:0
运行
复制
SELECT dep_obj_id, dep_obj_type FROM __all_tenant_dependency WHERE tenant_id = 0 AND ref_obj_id = 500037
unsetunset步骤 3:创建隐藏表(INSERT INTO __all_table)unsetunset
  • 作用: 创建隐藏表(table_id=500040table_name=__hidden_500040_500037table_schema),复制 t10(table_id=500037)结构,准备应用列 a 类型变更(varchar(12) → int(11))。association_table_id=500037 关联原表,tablet_id=200015 为新 tablet。
  • 系统状态: 隐藏表创建,ALTER 隔离开始。
代码语言:javascript
代码运行次数:0
运行
复制
INSERT INTO __all_table (
  tenant_id: 0
  table_id: 500040
  table_name: X'5F5F68696464656E5F3530303034305F3530303033377461626C655F736368656D61' (解码后含义: __hidden_500040_500037table_schema)
  schema_version: 1754385856456352
  association_table_id: 500037
  tablet_id: 200015
)
unsetunset步骤 4:定义隐藏表列(INSERT INTO __all_column)unsetunset
  • 作用:应用 ALTER 后的结构,列 a 从 varchar(12) 改为 int(11),其他列(b、c、d)保持 int(11)。
    • 独占锁确保列定义一致性,use_plan_cache(none) 避免缓存干扰。
  • 系统状态:隐藏表 500040 的 schema 完成,含新列 a 类型。
代码语言:javascript
代码运行次数:0
运行
复制
INSERT /*+use_plan_cache(none)*/ INTO __all_column (
  -- 列 a
  table_id: 500040
  column_id: 16
  column_name: X'61' (解码后含义: a)
  schema_version: 1754385856456352
)
unsetunset步骤 5:记录 DDL 操作(INSERT INTO __all_ddl_operation)unsetunset
  • 作用:
    • OPERATION_TYPE=4:创建隐藏表 500040(schema_version=1754385856456352)。
    • OPERATION_TYPE=3:修改原表 500037(schema_version=1754385856538896)。
    • 记录 ALTER 操作的创建(隐藏表)、修改(t10)和同步事件,确保变更可追溯。
  • 系统状态:DDL 操作日志化,schema_version 递增。
代码语言:javascript
代码运行次数:0
运行
复制
INSERT INTO __all_ddl_operation (
  SCHEMA_VERSION: 1754385856456352
  DATABASE_ID: 500002
  TABLE_ID: 500040
  OPERATION_TYPE: 4 (创建表)
)

INSERT INTO __all_ddl_operation (
  SCHEMA_VERSION: 1754385856538896
  DATABASE_ID: 500002
  TABLE_ID: 500037
  OPERATION_TYPE: 3 (修改表)
)
unsetunset步骤 6:更新用户表 t10(UPDATE __all_table)unsetunset
  • 作用:将 t10 的元数据关联到隐藏表 500040(含新 schema:列 a 为 int(11)),准备替换原表。独占锁确保一致性。
  • 系统状态:t10 的元数据更新,指向新 schema。
代码语言:javascript
代码运行次数:0
运行
复制
UPDATE __all_table SET (
  schema_version: 1754385856538896
  association_table_id: 500040
  define_user_id: 200001
  table_name: X'743130' (解码后含义: t10)
  tablet_id: 200014
  object_status: 1
) WHERE tenant_id = 0 AND table_id = 500037
unsetunset步骤 7:验证 Schema 一致性(SELECT __all_table_history EXCEPT __all_table)unsetunset
  • 作用:比较 __all_table_history__all_tabletable_id=500037 的元数据,验证 schema_version=1754385856538896。
    • 确保 ALTER 操作(列 a 变更)在 t10 和隐藏表 500040 间一致,检查索引元数据完整性。
  • 系统状态:schema 验证通过,ALTER 操作生效。
代码语言:javascript
代码运行次数:0
运行
复制
SELECT tenant_id, table_id, schema_version, table_name, database_id, table_type, ...
  FROM __all_table_history
 WHERE tenant_id = 0 AND table_id = 500037 AND schema_version = 1754385856538896
EXCEPT
SELECT tenant_id, table_id, schema_version, table_name, database_id, table_type, ...
  FROM __all_table
 WHERE tenant_id = 0 AND table_id = 500037;
步骤 8:检查全局状态(SELECT __all_core_table FOR UPDATE)
  • 作用:检查全局 DDL 状态,同步 ALTER 操作(列 a 变更)到系统范围,防止并发 DDL 干扰。
  • 系统状态:全局同步完成,准备替换 table_id。
代码语言:javascript
代码运行次数:0
运行
复制
SELECT column_value FROM __all_core_table WHERE TABLE_NAME = '__all_global_stat' AND COLUMN_NAME = 'ddl_epoch' FOR UPDATE
步骤 9:完成 DDL 同步
  • 作用:标志 ALTER 操作完成,table_id 从 500037 切换到 500040(新 schema,列 a 为 int(11))。
    • 1503 代表事务边界,标记 DDL 事务完成和全局 schema 同步。
  • 系统状态:DDL 操作结束,table_id 更新。
代码语言:javascript
代码运行次数:0
运行
复制
INSERT INTO __all_ddl_operation (
  SCHEMA_VERSION: 1754385865777640
  TENANT_ID: 0
  EXEC_TENANT_ID: 1006
  USER_ID: 0
  DATABASE_ID: 0
  TABLE_ID: 0
  TABLE_NAME: X''
  OPERATION_TYPE: 1503
  DDL_STMT_STR: X''
  gmt_modified: now(6)
)
unsetunset步骤 10:表锁释放(查询结果为空)unsetunset
代码语言:javascript
代码运行次数:0
运行
复制
[2025-08-05 17:24:26.632] Result is empty
2.2.2 观察 DDL 进度和步骤解读

执行的 sql 内容(脚本执行,间隔 10ms 执行一次,重复输出省略)。

代码语言:javascript
代码运行次数:0
运行
复制
select * from oceanbase.gv$session_longops\G
代码语言:javascript
代码运行次数:0
运行
复制
[2025-08-05 17:24:16.706] Query Results:
----------------------------------------
----------------------------------------
*************************** 1. row ***************************
             SID: 2025-08-05 17:24:16
输出为空,省略...
*************************** 2. row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modify column
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-05 17:24:17
 ELAPSED_SECONDS: 0
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-05 17:24:17
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: PREPARE
----------------------------------------
[2025-08-0517:24:16.863] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:16
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 0
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:17
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: WAIT TRANS END, PENDING_TX_ID: 0
----------------------------------------
[2025-08-0517:24:17.032] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:17
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 0
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:17
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: OBTAIN SNAPSHOT
----------------------------------------
[2025-08-0517:24:17.203] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:17
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 1
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:17
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: REPLICA BUILD, ROW_SCANNED: 0, ROW_SORTED: 0, ROW_INSERTED: 0
----------------------------------------
中间输出省略...
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:19
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 3
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:19
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: REPLICA BUILD, ROW_SCANNED: 300000, ROW_SORTED: 600000, ROW_INSERTED: 300000
----------------------------------------
[2025-08-0517:24:19.508] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:19
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 3
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:20
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: COPY DEPENDENT OBJECTS, CHILD TASK IDS: [ ]
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:20
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: createindex
          TARGET: 500041
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:20
 ELAPSED_SECONDS: 0
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:20
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33632, STATUS: WAIT TRANS END, PENDING_TX_ID: 0
*************************** 3.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 3
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:20
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: COPY DEPENDENT OBJECTS, CHILD TASK IDS: [ 33632 ]
----------------------------------------
[2025-08-0517:24:20.146] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:20
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: createindex
          TARGET: 500041
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:20
 ELAPSED_SECONDS: 0
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:20
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33632, STATUS: REPLICA BUILD, ROW_SCANNED: 0, ROW_SORTED: 0, ROW_INSERTED: 0
*************************** 3.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 4
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:20
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: COPY DEPENDENT OBJECTS, CHILD TASK IDS: [ 33632 ]
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:22
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 6
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:23
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: COPY DEPENDENT OBJECTS, CHILD TASK IDS: [ 3363233861 ]
*************************** 3.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: createindex
          TARGET: 500042
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:22
 ELAPSED_SECONDS: 0
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:23
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33861, STATUS: REPLICA BUILD, ROW_SCANNED: 0, ROW_SORTED: 0, ROW_INSERTED: 0
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:24
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 8
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:24
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: COPY DEPENDENT OBJECTS, CHILD TASK IDS: [ 3363233861 ]
*************************** 3.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: createindex
          TARGET: 500042
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:22
 ELAPSED_SECONDS: 2
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:24
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33861, STATUS: REPLICA BUILD, ROW_SCANNED: 300000, ROW_SORTED: 300000, ROW_INSERTED: 300000
----------------------------------------
[2025-08-0517:24:24.407] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:24
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 8
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:24
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: COPY DEPENDENT OBJECTS, CHILD TASK IDS: [ 3363233861 ]
*************************** 3.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: createindex
          TARGET: 500042
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:22
 ELAPSED_SECONDS: 2
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:24
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33861, STATUS: ENABLEINDEX
----------------------------------------
[2025-08-0517:24:24.748] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:24
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 8
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:25
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: COPY DEPENDENT OBJECTS, CHILD TASK IDS: [ 3363233861 ]
*************************** 3.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: createindex
          TARGET: 500042
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:22
 ELAPSED_SECONDS: 2
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:25
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33861, STATUS: CLEAN ONSUCCESS
----------------------------------------

[2025-08-0517:24:24.936] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:24
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 8
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:25
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: TAKE EFFECT
----------------------------------------
[2025-08-0517:24:25.482] Query Results:
----------------------------------------
----------------------------------------
*************************** 1.row ***************************
             SID: 2025-08-0517:24:25
输出为空,省略...
*************************** 2.row ***************************
             SID: -1
        TRACE_ID: YB420ABA403E-000638C4C465F278-0-0
          OPNAME: modifycolumn
          TARGET: 500040
          SVR_IP: 10.186.64.61
        SVR_PORT: 2882
      START_TIME: 2025-08-0517:24:17
 ELAPSED_SECONDS: 9
  TIME_REMAINING: 0
LAST_UPDATE_TIME: 2025-08-0517:24:26
         MESSAGE: TENANT_ID: 1006, TASK_ID: 33491, STATUS: CLEAN ONSUCCESS
----------------------------------------

不同时间点对应的操作:

  • 2025-08-05 17:24:17 数据库开始修改表(编号: 500040)的列结构,任务编号 33491,状态:PREPARE → WAIT TRANS END → OBTAIN SNAPSHOT → REPLICA BUILD(开始处理表数据)。
  • 2025-08-05 17:24:19 表修改任务(编号: 33491)状态:REPLICA BUILD,处理30万行数据(扫描、排序、插入),然后进入 COPY DEPENDENT OBJECTS(准备更新相关索引)。
  • 2025-08-05 17:24:20 创建第一个索引(编号: 500041,任务编号: 33632),状态:WAIT TRANS END → REPLICA BUILD(开始构建索引数据)。 表修改任务(编号: 33491)状态:COPY DEPENDENT OBJECTS,包含子任务 33632。
  • 2025-08-05 17:24:22 - 17:24:24 创建第二个索引(编号: 500042,任务编号: 33861),状态:REPLICA BUILD(处理30万行数据)→ ENABLE INDEX → CLEAN ON SUCCESS(完成并清理)。 表修改任务(编号: 33491)状态:COPY DEPENDENT OBJECTS,新增子任务 33861。
  • 2025-08-05 17:24:24 - 17:24:26 表修改任务(编号: 33491)状态:TAKE EFFECT → CLEAN ON SUCCESS,完成所有索引更新,应用更改并清理资源,总耗时 9 秒。
2.2.3 DDL 执行成功了么?
代码语言:javascript
代码运行次数:0
运行
复制
obclient [evan_db]> select b.table_name,a.tenant_id,a.task_id,a.object_id,a.target_object_id,a.ret_code,a.affected_rows,a.user_message,a.dba_message,a.ddl_type,a.gmt_create,a.gmt_modified from oceanbase.__all_ddl_error_message a join oceanbase.__all_table b on a.object_id = b.table_id order by a.gmt_modified desc limit 1;
+------------+-----------+---------+-----------+------------------+----------+---------------+----------------+----------------+----------+----------------------------+----------------------------+
| table_name | tenant_id | task_id | object_id | target_object_id | ret_code | affected_rows | user_message   | dba_message    | ddl_type | gmt_create                 | gmt_modified               |
+------------+-----------+---------+-----------+------------------+----------+---------------+----------------+----------------+----------+----------------------------+----------------------------+
| t10        |         0 |   33491 |    500040 |               -1 |        0 |             0 | Successful ddl | Successful ddl |     1001 | 2025-08-05 17:24:25.966025 | 2025-08-05 17:24:25.966025 |
+------------+-----------+---------+-----------+------------------+----------+---------------+----------------+----------------+----------+----------------------------+----------------------------+
1 row in set (0.057 sec)

此时,ret_code = 0user_message = Successful ddldba_message = Successful ddl,可以认为 DDL 执行成功。

查看表结构。

代码语言:javascript
代码运行次数:0
运行
复制
obclient [evan_db]> show create table t10;
| Table | Create Table                                                   
| t10   | CREATE TABLE `t10` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
UNIQUE KEY `c_idx` (`c`) BLOCK_SIZE 16384 LOCAL,
KEY `idx_b` (`b`) BLOCK_SIZE 16384 LOCAL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
1 row in set (0.035 sec)

可以看到 a 列 由 varchar(12) 转为 int(11)。

查看表的 table_id

代码语言:javascript
代码运行次数:0
运行
复制
obclient [evan_db]> select distinct(table_id) from oceanbase.dba_ob_table_locations where table_name = 't10';
+----------+
| table_id |
+----------+
|   500040 |
+----------+
1 row in set (0.014 sec)

可以看到,由于是 Offline DDL , table_id 已经由 500037 变成了 500040。

2.3 DDL 过程(日志观察)

若未及时查询视图或想查看更为详细的步骤,可以通过日志打印信息观察 DDL 过程。

2.3.1 前置操作

基于 gv$session_longops 查询结果的 trace_id 将涉及的信息保存到文本文件。

代码语言:javascript
代码运行次数:0
运行
复制
grep YB420ABA403E-000638C4C465F278-0-0 observer.log* > /tmp/YB420ABA403E-000638C4C465F278-0-0_DDL.txt
2.3.2 日志关键信息解读
unsetunset2.3.2.1 DDL 并行计算unsetunset
代码语言:javascript
代码运行次数:0
运行
复制
observer.log:[2025-08-05 17:24:16.323510] INFO  [SQL.RESV] calc_ddl_parallelism (ob_ddl_resolver.cpp:11093) [21619][T1006_L0_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=9] calc ddl parallelism(parallelism=1)

可见 parallelism=1,表明 DDL 操作的并行度为 1,即串行执行,没有并行。

unsetunset2.3.2.2 施加表锁unsetunset
代码语言:javascript
代码运行次数:0
运行
复制
observer.log:[2025-08-05 17:24:16.417567] INFO  [STORAGE.TABLELOCK] send_rpc_task_ (ob_table_lock_service.cpp:2125) [22578][T1006_L0_G20][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=98] send table lock rpc(ret=0, ret="OB_SUCCESS", retry_ctx.send_rpc_count_=1, addr="10.186.64.62:2882", request={task_type_:0, lsid_:{id:1}, params_:[{lock_id:{obj_type:1, obj_id:500037}, lock_mode:1, owner_id:{id:33491}, op_type:2, is_deadlock_avoid_enabled:false, is_try_lock:true, expired_time:1754387356415961, schema_version:0}], tx_desc_:{this:0x7f0fec5cb8a0, tx_id:{txid:33490}, state:2, addr:"10.186.64.62:2882", tenant_id:1006, session_id:1, assoc_session_id:1, xid:NULL, xa_mode:"", xa_start_addr:"0.0.0.0:0", access_mode:0, tx_consistency_type:0, isolation:1, snapshot_version:{val:18446744073709551615, v:3}, snapshot_scn:0, active_scn:1754385856406340, op_sn:6, alloc_ts:1754385856406340, active_ts:1754385856406340, commit_ts:-1, finish_ts:-1, timeout_us:999917948, lock_timeout_us:-1, expire_ts:1754386856324288, coord_id:{id:-1}, parts:[{id:{id:1}, addr:"10.186.64.62:2882", epoch:515276525793764, first_scn:1754385856409625, last_scn:1754385856409625, last_touch_ts:1}], exec_info_reap_ts:0, commit_version:{val:18446744073709551615, v:3}, commit_times:0, commit_cb:null, cluster_id:1730815150, cluster_version:17180000522, flags_.SHADOW:false, flags_.INTERRUPTED:false, flags_.BLOCK:false, flags_.REPLICA:false, can_elr:false, cflict_txs:[], abort_cause:0, commit_expire_ts:0, commit_task_.is_registered():false, modified_tables:[], ref:2}})
unsetunset2.3.2.3 等待活跃事务结束unsetunset

打印日志时存在活跃事务才会输出结果。

代码语言:javascript
代码运行次数:0
运行
复制
[root@10-186-64-62 tmp]# grep "WAIT_TRANS_END" YB420ABA403E-000638C4C465F278-0-0_DDL.txt
[root@10-186-64-62 tmp]# 
unsetunset2.3.2.4 创建隐藏表并进行数据迁移unsetunset
  1. 表明正在执行表重定义操作,从源表 t10 (table_id=500037) 向隐藏表 __hidden_500040_500037table_schema(table_id=500040)迁移数据。
    • 通过隐藏表的名称我们看出当本次 DDL 完成之后,t10 的 table_id 会变成 500040,相当于将隐藏表更名为 t10 ,而原表会进行删除。
  2. 使用快照(snapshot_version=1754385856939933337)读取数据,确保数据一致性。
  3. 按照第一个列(__pk_increment)排序,确保插入的数据按顺序排列。
代码语言:javascript
代码运行次数:0
运行
复制
[root@10-186-64-62 tmp]#  grep 'INTO `evan_db`.`__hidden_500040_500037table_schema' YB420ABA403E-000638C4C465F278-0-0_DDL.txt
observer.log:[2025-08-05 17:24:19.380073] INFO  [SERVER] process_final (ob_inner_sql_connection.cpp:662) [21600][T1006_L0_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=40] slow inner sql(last_ret=0, sql={ObIExecutor:, sql:"INSERT /*+ monitor enable_parallel_dml parallel(1) opt_param('ddl_execution_id', 0) opt_param('ddl_task_id', 33491) opt_param('enable_newsort', 'false') use_px */INTO `evan_db`.`__hidden_500040_500037table_schema`(`__pk_increment`, `a`, `b`, `c`, `d`) SELECT /*+ index(`t10` primary) ob_ddl_schema_version(`t10`, 1754385856538896) */ `__pk_increment` AS `__pk_increment`, `a` AS `a`, `b` AS `b`, `c` AS `c`, `d` AS `d` from `evan_db`.`t10` as of snapshot 1754385856939933337 order by  1"}, process_time=2048956)
unsetunset2.3.2.5 分配宏块并写入数据unsetunset
代码语言:javascript
代码运行次数:0
运行
复制
observer.log:[2025-08-05 17:24:17.626133] INFO  [STORAGE.BLKMGR] alloc_block (ob_block_manager.cpp:318) [603][T1006_PX_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=27] successfully alloc block(macro_id=[9078](ver=0,mode=0,seq=43366))
observer.log:[2025-08-05 17:24:17.630278] INFO  [STORAGE.BLKMGR] async_write (ob_macro_block_handle.cpp:178) [603][T1006_PX_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=40] Async write macro block(macro_id_=[9078](ver=0,mode=0,seq=43366))
unsetunset2.3.2.6 排序操作unsetunset
  1. 首先在内存中进行排序,若内存不足,排序操作会将数据写入磁盘(dumped=true)。
  2. 为排序数据分配磁盘临时文件,后续数据将写入该文件中。
代码语言:javascript
代码运行次数:0
运行
复制
observer.log:[2025-08-05 17:24:17.835883] INFO  [SQL.ENG] preprocess_dump (ob_sort_op_impl.cpp:968) [603][T1006_PX_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=195] trace sort need dump(dumped=true, mem_context_->used()=30742088, get_memory_limit()=1473665, profile_.get_cache_size()=30242949, profile_.get_expect_size()=1473665, sql_mem_processor_.get_data_size()=26673152)
...
observer.log:[2025-08-05 17:24:18.004129] INFO  [STORAGE] open (ob_tmp_file.cpp:1393) [603][T1006_PX_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=49] succeed to open a tmp file(fd=381, dir=17185094, common::lbt()="0x13978d65 0xfb1005f 0xfb0f383 0xcf552eb 0xcf4bd7c 0xcf4c675 0xcf4cd45 0x57ae0d5 0xcf4e46f 0xd2ae0d6 0x578b997 0xd2a766e 0xd2a66fe 0x571fd54 0x56f1652 0x55b805c 0x5884afc 0xd4780da 0xd476b57 0x5884397 0xd210a3d 0x55b8c6c 0xb95484d 0x55b80ae 0xb9a6d7f 0xb953514 0xb995a91 0x5658889 0xddb6b44 0xdded6b4 0xa945ce2 0xa9458eb 0x13c371cf 0x7f105c714ea5 0x7f105c43db0d")
observer.log:[2025-08-05 17:24:18.004175] INFO  [SQL.ENG] write_file (ob_chunk_datum_store.cpp:2036) [603][T1006_PX_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=41] open file success(io_.fd=381, io_.dir_id=17185094)
unsetunset2.3.2.7 重建索引表unsetunset
  1. __idx_500040_c_idx 为例,索引表 __idx_500123_idx_b 同理
  2. 将数据从隐藏表( __hidden_500040_500037table_schema )插入到索引表( __idx_500040_c_idx ),该 SQL 使用快照版本 ( 1754385859935671612 )读取隐藏表数据,并按列 c__pk_increment 排序后插入索引表。
代码语言:javascript
代码运行次数:0
运行
复制
observer.log:[2025-08-05 17:24:21.634195] INFO  [SERVER] process_final (ob_inner_sql_connection.cpp:662) [21591][T1006_L5_G0][T1006][YB420ABA403E-000638C4C465F278-0-0] [lt=36] slow inner sql(last_ret=0, sql={ObIExecutor:, sql:"INSERT /*+ monitor enable_parallel_dml parallel(1) opt_param('ddl_execution_id', 0) opt_param('ddl_task_id', 33632) opt_param('enable_newsort', 'false') use_px */INTO`evan_db`.`__idx_500040_c_idx`(`c`, `__pk_increment`) SELECT/*+ index(`__hidden_500040_500037table_schema` primary)  */`c`AS`c`, `__pk_increment`AS`__pk_increment`from`evan_db`.`__hidden_500040_500037table_schema`asofsnapshot1754385859935671612orderby1, 2"}, process_time=1459268)
unsetunset2.3.2.8 确认 DDL 执行结果unsetunset
  1. 由于日志打印频率问题,本次日志未检索到输出信息。
  2. 前方查询 oceanbase.__all_ddl_error_message 可确认 DDL 执行成功。
代码语言:javascript
代码运行次数:0
运行
复制
[root@10-186-64-62 tmp]# grep "report_ddl_error_message" YB420ABA403E-000638C4C465F278-0-0_DDL.txt
[root@10-186-64-62 tmp]# 

3. 疑问解答

我们经过对 DDL 操作的详细分析,可以对开篇的两个提问进行回答。

Q1:OceanBase 执行 Offline DDL 会加表锁吗?

通常会施加表锁,尤其是涉及表结构变更或数据重写的操作,具体可根据前方步骤观察 gv$ob_locks 视图进行确认。

Q2:执行 Offline DDL 的耗时如何评估?

“与数据量有关,需要重整数据” 或 “是否有活跃事务有关”,可查看官网关于 DDL 操作的耗时评估依据表格。

较为准确的耗时评估方法:

  1. 建一个同等规格的租户,将数据通过 OMS 迁移过去,在新租户上执行 DDL 操作并记录耗时。
  2. 克隆租户并在新租户上执行 DDL 操作并记录耗时(OB 版本需 > 4.3.0)。
  3. 注意可能需要模拟业务 DML 操作以增加耗时评估准确性。

4. 温馨提示

  1. OceanBase 数据库当前版本,Online DDL 操作暂不支持混合类型操作。
    • 例如,单独添加列和单独添加索引时都是支持的 Online DDL 操作,但同时添加列和添加索引不支持 Online DDL 操作,仅支持 Offline DDL操作。
  2. 在 DDL 操作前后执行如下查看 table_id 是否发生变化,如果没有变化,说明是 Online DDL,如果发生变化,则是 Offline DDL。
    • 注意:此判断方法对表操作和分区操作无效!
  3. DDL 操作尽量在业务低峰期执行。

5. 参考资料

  1. 《Online DDL 和 Offline DDL 操作》:https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000252799
  2. 《DDL 状态未决的原因与处理》:https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000569397?back=kb
  3. 《GV$OB_LOCKS》:https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000219780
  4. 《OceanBase DDL的 实现以及跟 MySQL 的简单对比》:https://open.oceanbase.com/blog/10844647504
  5. 《OB 社区版源码-ob_schema_service.h》:https://github.com/oceanbase/oceanbase/blob/develop/src/share/schema/ob_schema_service.h
  6. 《OB 社区版源码-ob_schema_service_sql_impl.cpp》:https://github.com/oceanbase/oceanbase/blob/develop/src/share/schema/ob_schema_service_sql_impl.cpp

本文关键字:#OceanBase #离线DDL

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱可生开源社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 来自客户的提问
    • 1.1 OceanBase 的 Offline DDL
    • 1.2 本文探究
  • 2. DDL 操作
    • 2.1 环境信息
    • 2.2 DDL 过程(视图观察)
      • 2.2.1 DDL 表锁解读
      • 步骤 8:检查全局状态(SELECT __all_core_table FOR UPDATE)
      • 步骤 9:完成 DDL 同步
      • 2.2.2 观察 DDL 进度和步骤解读
      • 2.2.3 DDL 执行成功了么?
    • 2.3 DDL 过程(日志观察)
      • 2.3.1 前置操作
      • 2.3.2 日志关键信息解读
  • 3. 疑问解答
    • Q1:OceanBase 执行 Offline DDL 会加表锁吗?
    • Q2:执行 Offline DDL 的耗时如何评估?
  • 4. 温馨提示
  • 5. 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档