基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
体验在线 DDL
更新时间:2026-10-27 15:56:46
新增列、创建索引等表结构变更,是业务迭代中最常见的数据库变更操作。传统数据库执行此类 DDL 时可能需要长时间锁表,业务读写被迫中断,变更往往只能安排在业务低峰期进行。OceanBase 数据库的多数常见 DDL 为在线 DDL(Online DDL):执行期间用户仍然可以对表进行读写操作。其中末尾加列等操作只需修改元数据;创建索引虽然需要重整数据、耗时与数据量有关,但执行期间业务读写可以继续。
本文将在一张具有持续读写业务的 100 万行测试表上执行新增列、创建索引等常见 DDL,观察 DDL 执行期间业务读写是否可继续,并验证变更后的表结构和查询结果。
本文将引导您完成以下体验:
- 准备持续读写的测试表:创建测试租户,导入 100 万行样例数据,并用 Shell 循环模拟持续读写业务。
- 体验在线加列:在业务持续读写期间执行末尾加列,验证 DDL 期间业务不中断。
- 体验在线创建索引:在业务持续读写期间创建索引,观察数据重整期间业务可继续。
- 验证在线性与变更结果:通过
table_id前后对比确认 DDL 为在线执行,并验证变更后的表结构与查询结果。
说明
本文基于 OceanBase 数据库 V5.0.1(社区版)撰写,文中相关命令和功能说明均以 V5.0.1 版本为准。本文以 部署 OceanBase 中使用 All in One 安装包部署的单节点环境为例,全部操作均可在单节点环境中完成。
前提条件
在开始本文操作前,确保满足以下条件:
| 项目 | 描述 |
|---|---|
| OceanBase 数据库 | 已使用 All in One 安装包完成部署并成功启动(部署名为 demo),参见 部署 OceanBase。 |
| 机器资源 | 建议不低于 4 核 CPU、16 GB 内存,磁盘剩余空间不低于 5 GB。 |
| obclient | 已安装 obclient 并可正常连接 OceanBase 数据库。使用 All in One 安装包部署时已自动安装。 |
| sys 租户访问 | 可使用 root 用户登录集群的 sys 租户执行管理操作。sys 租户 root 用户的密码为部署时随机生成的密码,可在部署输出或 obd cluster display demo 命令的输出中查看。 |
| 多租户基础 | 本文会创建一个体验租户,租户与资源池的概念参见 体验多租户与资源隔离。 |
在线 DDL 简介
OceanBase 数据库将 DDL 操作分为在线与离线两类:
- 在线 DDL(Online DDL):执行 DDL 操作时,用户仍然可以访问数据库,包括读取和写入操作。对业务的影响小,支持并发访问和更少的锁定,能够减少停机时间。
- 离线 DDL(Offline DDL):执行 DDL 操作时会锁定相关表,导致用户无法访问数据库的相关数据。执行期间会造成一定的停机时间,影响正常的查询和事务处理。
常见 DDL 操作的在线情况如下(摘自官方 MySQL 模式支持列表):
| 操作 | 在线情况 | 耗时特点 |
|---|---|---|
增加索引(CREATE INDEX / ALTER TABLE ... ADD INDEX) |
在线 | 与数据量有关,需要重整数据 |
末尾加列(ALTER TABLE ... ADD COLUMN,不指定位置) |
在线 | 只需要修改元数据 |
| 设列默认值、重命名表、重命名索引 | 在线 | 只需要修改元数据 |
| 修改列类型 | 离线 | 与数据量有关,需要重整数据 |
| 添加自增列 | 离线 | 与数据量有关,需要重整数据 |
| 添加/删除主键 | 离线 | 与数据量有关,需要重整数据 |
离线 DDL 的原理:离线 DDL 采用"重建表"的方式,新建一张对用户不可见的临时隐藏表,将原表数据迁移到新表后重命名并删除旧表。因此离线 DDL 完成后表的 table_id 会发生变化,且执行期间不允许 DML 操作。
在线性判断方法:在 DDL 操作前后查询 table_id,如果前后一致,说明该 DDL 为在线操作;如果发生变化,则为离线操作:
SELECT DISTINCT(table_id) FROM oceanbase.DBA_OB_TABLE_LOCATIONS WHERE table_name = 'xxx';
说明
完整的在线/离线 DDL 操作列表及判断步骤请参见 Online DDL 和 Offline DDL 操作。
创建测试租户
使用 root 用户登录集群的 sys 租户(密码为部署时随机生成的密码,获取方式见上文前提条件):
obclient -h127.0.0.1 -uroot@sys -P2881 -p -A oceanbase创建一个 2C4G 规格的资源单元、资源池和租户
ddl_tenant:obclient [oceanbase]> CREATE RESOURCE UNIT ddl_unit MAX_CPU=2, MIN_CPU=2, MEMORY_SIZE='4G'; obclient [oceanbase]> CREATE RESOURCE POOL ddl_pool UNIT='ddl_unit', UNIT_NUM=1, ZONE_LIST=('zone1'); obclient [oceanbase]> CREATE TENANT IF NOT EXISTS ddl_tenant CHARSET='utf8mb4', PRIMARY_ZONE='zone1', RESOURCE_POOL_LIST=('ddl_pool') SET ob_tcp_invited_nodes='%';租户创建需要数十秒(本文环境实测约 6 秒),期间命令行没有输出,属正常现象,等待命令返回即可。
登录
ddl_tenant。新创建的租户 root 用户默认密码为空,提示输入密码时直接回车即可,后续操作均在此租户中进行:obclient -h127.0.0.1 -uroot@ddl_tenant -P2881 -p -A test
准备测试表与样例数据
在
ddl_tenant的会话中创建测试表ddl_tbl:obclient [test]> CREATE TABLE ddl_tbl ( -> id BIGINT PRIMARY KEY, -> user_name VARCHAR(32), -> city VARCHAR(32), -> amount INT -> );在部署 OceanBase 的机器上,执行以下命令生成 100 万行测试数据:
seq 1 1000000 | awk '{printf "%d,user_%06d,city_%d,%d\n", $1, $1, $1%20, $1%1000}' > /tmp/ddl_sample.csv该命令生成包含以下字段的 CSV 文件:
id(递增编号)、user_name(用户名)、city(20 个城市值循环取值)、amount(金额)。检查数据文件:
head -3 /tmp/ddl_sample.csv wc -l /tmp/ddl_sample.csvhead命令返回的前 3 行数据如下:1,user_000001,city_1,1 2,user_000002,city_2,2 3,user_000003,city_3,3wc -l返回1000000表示数据行数正确。在操作系统终端中执行以下命令,将数据导入
ddl_tbl。连接时通过--local-infile参数启用客户端本地文件导入,无需在服务端配置文件访问路径:obclient -h127.0.0.1 -uroot@ddl_tenant -P2881 --local-infile -A test -e "SET ob_query_timeout=3600000000; LOAD DATA LOCAL INFILE '/tmp/ddl_sample.csv' INTO TABLE ddl_tbl FIELDS TERMINATED BY ',';"回到 obclient 会话,校验导入行数:
obclient [test]> SELECT COUNT(*) FROM ddl_tbl;返回结果为
1000000,与源数据文件行数一致,表示数据完整入库:+----------+ | COUNT(*) | +----------+ | 1000000 | +----------+ 1 row in set
说明
LOAD DATA LOCAL INFILE从客户端本地读取数据文件,与 体验并行导入与高压缩 中使用的服务端LOAD DATA INFILE(需配置secure_file_priv)不同,详细信息请参见 使用 LOAD DATA 语句导入数据。- 命令中的
SET ob_query_timeout=3600000000用于将本会话的语句超时时间临时调整为 1 小时,避免大批量导入耗时超过语句默认超时时间(10 秒)导致执行失败。 - 新建租户的 root 用户默认密码为空,以上命令可直接执行;如已设置密码,请在命令中追加
-p密码(如-p'******')。
模拟持续读写业务
后续 DDL 体验需要一个对测试表持续读写的业务负载。本文使用 Shell 循环模拟:每秒向 ddl_tbl 写入一行新数据(INSERT),并按主键查询一次刚写入的数据(SELECT)。
新开一个操作系统终端(下称终端 A),执行以下命令启动业务模拟循环:
i=1000001 while true; do obclient -h127.0.0.1 -uroot@ddl_tenant -P2881 -A test -e \ "INSERT INTO ddl_tbl(id, user_name, city, amount) VALUES($i, 'user_live', 'city_live', 500); SELECT id, user_name, amount FROM ddl_tbl WHERE id = $i;" \ > /dev/null 2>&1 \ && echo "$(date '+%H:%M:%S') 读写成功 id=$i" \ || echo "$(date '+%H:%M:%S') 读写失败 id=$i" i=$((i+1)) sleep 1 done命令说明:
obclient -e:执行语句并退出。循环中的每一轮通过一条 INSERT 完成写入、一条主键点查完成读取,语句均执行成功时输出"读写成功",任一语句失败时输出"读写失败"。- 查询结果被重定向到
/dev/null,仅通过命令退出码判断读写是否成功,输出更简洁。 - 循环从
id=1000001开始写入,与已导入的 100 万行数据不冲突。 - 需要停止业务模拟时,按 Ctrl+C 结束循环。
循环启动后,终端 A 每秒输出一行结果(时间与编号以实际输出为准):
10:30:01 读写成功 id=1000001 10:30:02 读写成功 id=1000002 10:30:03 读写成功 id=1000003保持终端 A 的循环持续运行,另开一个终端(下称终端 B)登录
ddl_tenant,后续 DDL 均在终端 B 的 obclient 会话中执行:obclient -h127.0.0.1 -uroot@ddl_tenant -P2881 -p -A test
说明
obclient -e、--local-infile 等命令行选项的详细说明请参见 OBClient 命令行选项参考。
体验在线加列
本节在终端 A 业务循环持续运行期间,为 ddl_tbl 执行末尾加列,验证 DDL 执行期间业务读写不中断。
操作步骤
在终端 B 的会话中,查询并记录 DDL 执行前的
table_id:obclient [test]> SELECT DISTINCT(table_id) FROM oceanbase.DBA_OB_TABLE_LOCATIONS WHERE table_name = 'ddl_tbl';返回结果示例如下(
table_id为系统自动分配的对象编号,请记录实际返回值,本文以500002为例):+----------+ | table_id | +----------+ | 500002 | +----------+ 1 row in set确认终端 A 的读写循环持续运行中,在终端 B 的会话中执行末尾加列:
obclient [test]> ALTER TABLE ddl_tbl ADD COLUMN remark VARCHAR(64) DEFAULT NULL; Query OK, 0 rows affected观察 DDL 执行期间终端 A 的输出:从开始执行加列到命令返回的全过程中,读写循环持续输出"读写成功",没有任何一轮出现"读写失败",表明业务读写未中断。
在终端 B 的会话中再次查询
table_id,与第 1 步记录的值对比:obclient [test]> SELECT DISTINCT(table_id) FROM oceanbase.DBA_OB_TABLE_LOCATIONS WHERE table_name = 'ddl_tbl';返回结果仍为
500002(以实际记录值为准),与 DDL 执行前一致,说明数据表没有被重建,本次加列为在线执行。查看变更后的表结构:
obclient [test]> DESCRIBE ddl_tbl;返回结果如下,新增列
remark已出现在表结构末尾:+-----------+-------------+------+------+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------+-------------+------+------+---------+-------+ | id | bigint(20) | NO | PRI | NULL | | | user_name | varchar(32) | YES | | NULL | | | city | varchar(32) | YES | | NULL | | | amount | int(11) | YES | | NULL | | | remark | varchar(64) | YES | | NULL | | +-----------+-------------+------+------+---------+-------+ 5 rows in set查询存量数据的新列取值。末尾加列只修改元数据,存量行读取时按列默认值返回:
obclient [test]> SELECT id, remark FROM ddl_tbl WHERE id = 500000;+--------+--------+ | id | remark | +--------+--------+ | 500000 | NULL | +--------+--------+ 1 row in set验证新列对新的写入立即生效:向
remark列写入数据并查询:obclient [test]> INSERT INTO ddl_tbl(id, user_name, city, amount, remark) VALUES(2000001, 'user_check', 'city_1', 100, 'online ddl works'); obclient [test]> SELECT id, user_name, remark FROM ddl_tbl WHERE id = 2000001;+---------+------------+------------------+ | id | user_name | remark | +---------+------------+------------------+ | 2000001 | user_check | online ddl works | +---------+------------+------------------+ 1 row in set
结果解读
| 观察项 | 预期现象 | 说明 |
|---|---|---|
| 业务读写 | DDL 执行期间读写循环持续输出"读写成功" | 末尾加列为在线 DDL,只修改元数据,不重整数据、不阻塞业务 |
| table_id | DDL 前后不变 | 数据表没有被重建,符合官方在线性判断方法 |
| 表结构 | remark 列出现在表结构末尾 |
变更结果通过 DESCRIBE 验证 |
| 存量数据 | remark 均为默认值 NULL |
元数据级变更,存量行读取时按默认值返回 |
| 新写入 | 可为 remark 指定任意值 |
变更对新数据立即生效 |
注意
- 本文演示的加列未指定列位置(默认追加到表末尾),为在线 DDL。带
AFTER/BEFORE/FIRST的指定位置加列在不同版本中的在线情况不同,执行前请在 Online DDL 和 Offline DDL 操作 中确认。 - 业务 SQL 通过显式指定列名(
INSERT INTO ddl_tbl(id, user_name, city, amount) ...)写入数据,加列后无需修改业务语句即可继续执行;使用INSERT INTO ddl_tbl VALUES(...)全列形式的语句在表列数变化后会执行失败。
体验在线创建索引
本节继续在业务循环运行期间,为 ddl_tbl 的 city 列创建索引。与末尾加列不同,创建索引需要重整数据、耗时与数据量有关,是观察"数据重整期间业务是否可继续"的典型场景。
操作步骤
确认终端 A 的读写循环持续运行中,在终端 B 的会话中执行以下语句,为
city列创建索引:obclient [test]> CREATE INDEX idx_ddl_city ON ddl_tbl(city); Query OK, 0 rows affected观察 DDL 执行期间终端 A 的输出:索引创建需要将 100 万行存量数据重整为索引数据,耗时与数据量有关;从开始创建到命令返回期间,读写循环持续输出"读写成功",业务读写未中断。
如需再次确认在线性,可参照上一节的方法查询
table_id:创建索引前后,数据表的table_id保持不变。查看变更后的表定义。在语句末尾使用
\G可将查询结果按行垂直显示,便于阅读较长的建表语句:obclient [test]> SHOW CREATE TABLE ddl_tbl\G返回结果如下(建表语句中的表属性与集群配置相关,请以实际结果为准,重点关注新增的
remark列与idx_ddl_city索引):*************************** 1. row *************************** Table: ddl_tbl Create Table: CREATE TABLE `ddl_tbl` ( `id` bigint(20) NOT NULL, `user_name` varchar(32) DEFAULT NULL, `city` varchar(32) DEFAULT NULL, `amount` int(11) DEFAULT NULL, `remark` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_ddl_city` (`city`) BLOCK_SIZE 16384 LOCAL ) ORGANIZATION INDEX DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE ENABLE_MACRO_BLOCK_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 1 row in set验证查询结果。统计
city列某个取值的行数:obclient [test]> SELECT COUNT(*) FROM ddl_tbl WHERE city = 'city_5';返回结果为
50000,与源数据分布一致(100 万行数据按 20 个城市值均匀分布,每个城市值 50000 行):+----------+ | COUNT(*) | +----------+ | 50000 | +----------+ 1 row in set在终端 A 按 Ctrl+C 停止业务模拟循环,回到终端 B 的会话统计循环期间写入的行数:
obclient [test]> SELECT COUNT(*) FROM ddl_tbl WHERE id > 1000000;返回的行数即业务模拟循环写入的总行数与前文验证写入的
id=2000001一行之和,与循环运行时长相关(本文实测循环运行约 100 秒写入 100 行,加上验证行共 101 行)。这些行在加列与创建索引两次 DDL 执行期间持续写入成功,进一步印证了 DDL 期间业务写入未中断。
结果解读
| 观察项 | 预期现象 | 说明 |
|---|---|---|
| 业务读写 | 索引创建期间读写循环持续输出"读写成功" | 增加索引为在线 DDL,重整数据的过程不阻塞业务 DML |
| DDL 耗时 | 与数据量有关 | 索引创建需要将存量数据重整为索引数据 |
| 索引结构 | 建表语句中出现 KEY idx_ddl_city (city) |
变更结果通过 SHOW CREATE TABLE 验证 |
| 查询结果 | city_5 行数为 50000 |
与导入数据的分布规律一致,数据重整未影响数据正确性 |
| 持续写入 | id > 1000000 的行数与循环时长相符 |
两次 DDL 期间业务写入持续成功 |
注意
- 索引名称在数据库(Database)范围内不能重复。
- 大数据量表创建索引的耗时与数据量有关,可在
CREATE INDEX语句中通过parallel(N)Hint 指定并行度加速索引创建(如CREATE /*+ parallel(4) */ INDEX ...),详细信息请参见 CREATE INDEX。 - 本文仅演示普通索引的在线创建,唯一索引、函数索引、全文索引等更多索引类型的创建方法请参见 创建索引。
下一步
完成本文的在线 DDL 体验后,您可以继续以下探索之旅:
- 回顾快速上手系列:至此,您已完成从部署、高并发 OLTP、HTAP、AI 场景、多租户、高可用、水平扩展、数据效率、数据保护到在线变更的完整体验,可从 部署 OceanBase 开始回顾。
- 深入学习 DDL 管理:完整的在线/离线 DDL 操作列表与在线性判断方法请参见 Online DDL 和 Offline DDL 操作。
- 深入学习索引管理:索引的查看与删除等操作请参见 查看索引。
相关文档
- 在线/离线 DDL 操作的完整列表与在线性判断方法请参见 Online DDL 和 Offline DDL 操作。
- 增加列、修改列、删除列等表结构变更操作请参见 更改表。
- 各类索引的创建方法请参见 创建索引。
CREATE INDEX语句的完整语法请参见 CREATE INDEX。ALTER TABLE语句的完整语法请参见 ALTER TABLE。- 索引的查看方法请参见 查看索引。
LOAD DATA LOCAL INFILE的使用说明请参见 使用 LOAD DATA 语句导入数据。DBA_OB_TABLE_LOCATIONS视图的字段说明请参见 DBA_OB_TABLE_LOCATIONS。obclient命令行选项的详细说明请参见 OBClient 命令行选项参考。