DataX 同步 StarRocks VARCHAR 超长导致数据丢失
1. 背景
线上有一个数据接入任务,通过 Airflow 调度 DataX 将 PostgreSQL 的某张主数据表全量同步到 StarRocks。
某天任务突然失败,Airflow 日志报错:
1 | java.io.IOException: Failed to flush data to StarRocks. |
关键信息:
- 报错发生在 StarRocks Stream Load 写入阶段,不是 DataX reader 读取阶段
- 报错的列(
name、short_name、biz_scope)在 StarRocks 目标表中定义为NOT NULL - 源库 PostgreSQL 中这些列 确实有值,不是 NULL
- 只有少量行(6 条)受影响,其余 8110 条正常写入
2. 排查过程
2.1 第一反应:源数据是 NULL?
看到 NULL value in non-nullable column,第一反应是源库数据就是 NULL。
直接查源库:
1 | SELECT id, name, length(name) as name_len |
结果:三条数据的 name 都有值,长度为 363 个字符,内容是一段包含中文、特殊字符的长字符串。
源数据不是 NULL,问题不在源端。
2.2 第二反应:分隔符冲突?
name 的值包含大量特殊字符(<>?:"{}|+_)(*&^%$#@!~ 等),而 StarRocks Stream Load 使用的是 CSV 模式,配置了自定义分隔符:
1 | "loadProps": { |
怀疑 name 值中包含 \x01 或 \x02,导致 Stream Load 解析时列错位。
验证方式:将 loadProps 改为 JSON 格式,彻底避免分隔符问题:
1 | "loadProps": { |
结果:换成 JSON 格式后,报错完全一样。同样的行、同样的列、同样的 NULL 错误。
不是分隔符冲突的问题。CSV 和 JSON 两种格式都报同样的错,说明问题在更上游。
2.3 第三反应:DataX reader 读出来就是 NULL?
既然源库有值,Stream Load 收到的却是 NULL,那是不是 DataX 的 PostgreSQL reader 在读取时把值丢了?
查看 DataX reader 源码,VARCHAR 类型的处理逻辑:
1 | // 伪代码:DataX reader 读取 VARCHAR 字段 |
resultSet.getString() 对于正常的 VARCHAR 数据不会返回 NULL(除非源数据本身就是 NULL)。而且 DataX 的统计信息显示 读出记录总数 8116,读写失败总数 0,说明 reader 端读取完全正常。
DataX reader 正常读到了所有数据,包括那 6 条”问题行”。数据在 reader 端没有丢失。
2.4 关键转折:对比源表和目标表的列定义
前面三个方向都排除了,回到最基本的问题:StarRocks 说收到了 NULL,但数据确实发过去了,那只有一种可能——StarRocks 在写入时主动丢弃了这个值。
什么情况下 StarRocks 会把一个非 NULL 的值变成 NULL?值超过了列定义的长度限制。
查 StarRocks 目标表的列定义:
1 | SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE |
| 列名 | 类型 | 最大长度 | 允许 NULL |
|---|---|---|---|
| name | VARCHAR | 255 | NO |
| short_name | VARCHAR | 255 | NO |
再看源数据的实际长度:
| id | name 长度(字符数) |
|---|---|
| 10001 | 363 |
| 10002 | 363 |
| 10003 | 363 |
源数据 name 有 363 个字符,StarRocks 的 name 列定义为 VARCHAR(255)。363 > 255,超长了。
2.5 根因确认:VARCHAR(n) 的语义差异
StarRocks 目标表是根据 PostgreSQL 源表的 DDL 自动生成的,两边都是 VARCHAR(255)。但问题在于:
PostgreSQL 和 StarRocks 对 VARCHAR(n) 中 n 的定义不同:
- PostgreSQL:n 表示 字符数,一个中文算 1 个字符
- StarRocks:n 表示 字节数,UTF-8 下一个中文占 3 个字节
所以同样是 VARCHAR(255):
| 数据库 | 最多存储中文字符数 | 最多存储字节数 |
|---|---|---|
| PostgreSQL | 255 个 | ~765 字节 |
| StarRocks | ~85 个 | 255 字节 |
而且 PostgreSQL 对 VARCHAR 长度的校验本身就比较宽松,源表中已经存在超过 255 个字符的历史数据(363 字符)。这些数据同步到 StarRocks 时,因为 UTF-8 编码后远超 255 字节,StarRocks 不会截断,而是直接将值置为 NULL,再加上 NOT NULL 约束,就报错了。
同样的问题也影响了 short_name 和 biz_scope 列。
3. 问题代码
后端项目中,根据源表 DDL 自动生成 StarRocks 建表语句的逻辑,直接将源库的 VARCHAR(n) 原样映射为 SR 的 VARCHAR(n),没有考虑两个数据库对长度单位的差异。
1 | // 伪代码:自动建表的类型映射逻辑 |
这个问题不仅影响 PostgreSQL,MySQL 的 VARCHAR(n) 同样是字符数语义(MySQL 5.0+ 之后),直接映射到 StarRocks 也会有同样的问题。
4. 修复方案
修改自动建表的类型映射逻辑
源库(PG / MySQL)的 VARCHAR(n) 映射到 StarRocks 时,长度应乘以 3,即 VARCHAR(n) → VARCHAR(n * 3)。
1 | // 伪代码:修复后的类型映射逻辑 |
完整映射规则:
| 源库类型 | 条件 | StarRocks 类型 |
|---|---|---|
| VARCHAR(n) | n * 3 ≤ 65533 | VARCHAR(n * 3) |
| VARCHAR(n) | n * 3 > 65533 | STRING |
| CHAR(n) | n * 3 ≤ 65533 | VARCHAR(n * 3) |
| CHAR(n) | n * 3 > 65533 | STRING |
| TEXT | — | STRING |
| MEDIUMTEXT(MySQL) | — | STRING |
| LONGTEXT(MySQL) | — | STRING |
| TINYTEXT(MySQL) | — | STRING |
65533 是 StarRocks VARCHAR 的最大字节长度限制。
5. 为什么这个 Bug 特别隐蔽?
这个 Bug 有几个特点让它很难被发现:
5.1 大部分数据正常
8116 条数据中只有 6 条超长,占比不到 0.1%。如果 DataX 配置了 errorLimit 或 max_filter_ratio,这些行会被 静默丢弃,任务显示成功,但数据已经丢了。
5.2 DataX 统计信息会误导
当配置了 max_filter_ratio 时,DataX 的任务统计会显示:
1 | 读出记录总数: 8116 |
看起来完全成功,但实际上 StarRocks 只写入了 8110 条。DataX 认为自己成功了,因为 Stream Load 在 max_filter_ratio 范围内返回了成功。
5.3 报错信息有误导性
NULL value in non-nullable column 'name' 这个错误信息会让人以为源数据是 NULL,或者 DataX 传输过程中丢了数据。实际上数据完整传过去了,是 StarRocks 在写入时因为超长主动丢弃了值。
5.4 源表定义和目标表定义”看起来一样”
两边都是 VARCHAR(255),表面上完全一致,但语义不同(字符 vs 字节)。如果不了解这个差异,很难想到是长度问题。
6. 总结
6.1 根因链
1 | 源库 VARCHAR(n) 的 n = 字符数 |
6.2 经验教训
不同数据库对 VARCHAR(n) 的长度语义不同。PostgreSQL 和 MySQL 是字符数,StarRocks 是字节数。跨库同步时必须做长度换算,建议统一乘以 3(UTF-8 最大字节数)。
StarRocks 对超长 VARCHAR 的处理是置 NULL 而非截断。这个行为和 MySQL 的 strict mode 类似,但更隐蔽,因为它不会在 DataX 层面报错,只在 Stream Load 的 Error URL 里才能看到。
不要依赖
max_filter_ratio来”容忍”错误。它会让数据静默丢失,DataX 任务显示成功但实际数据不完整。生产环境建议设为 0,有问题就报错。DataX 的”读写失败总数: 0”不代表数据完整。Stream Load 在
max_filter_ratio范围内返回成功,DataX 就认为全部成功了,不会感知到被过滤的行。
跨数据库同步时,VARCHAR(n) 的 n 不是一个通用概念。字符数 ≠ 字节数,这个差异会导致数据静默丢失。


