使用 Spark 在两个 DM8 数据库之间迁移数据时,即使源表和目标表字段都定义为 VARCHAR(20),写入目标库时仍可能出现 out of length、Param incompatible 等异常。
这种情况通常不是字段定义不一致,而是源库和目标库字符集不同导致的。
例如:
VARCHAR(20)VARCHAR(20)对于同一段中文数据,在不同字符集下占用的字节数可能不同。
以字符串:
数据迁移字符编码测试
为例,10 个常用汉字在 GB18030 中通常占用 20 字节,而转换为 UTF-8 后通常需要 30 字节。
因此,这条数据可以正常存储在 GB18030 数据库的 VARCHAR(20) 中,但迁移到 UTF-8 数据库后,目标字段仍为 VARCHAR(20) 时就可能出现长度超限。
val value = "数据迁移字符编码测试"
val sourceBytes =
value.getBytes(Charset.forName("GB18030")).length
val targetBytes =
value.getBytes(StandardCharsets.UTF_8).length
计算结果约为:
GB18030:20 字节
UTF-8:30 字节
也就是说,字段定义相同,并不代表实际能够保存的数据量相同。
首先确认源库和目标库的字符集:
SELECT SF_GET_UNICODE_FLAG();
常见返回值:
0:GB18030
1:UTF-8
2:EUC-KR
同时确认字符字段长度采用字节还是字符计算:
SELECT PARA_VALUE
FROM V$DM_INI
WHERE PARA_NAME = 'LENGTH_IN_CHAR';
通常:
0:按字节计算
1:按字符计算
只有在按字节计算长度时,字符集转换才容易出现上述 VARCHAR(20) 容量不一致的问题。
如果目标表不存在,由平台根据源表结构自动创建目标表,则在生成目标表 DDL 时,应根据源端和目标端字符集适当扩大字符字段长度。
例如,从 GB18030 迁移到 UTF-8 时,对于常用中文字符:
GB18030:一个汉字通常约 2 字节
UTF-8:一个汉字通常约 3 字节
因此可以按照约 1.5 倍进行长度补偿:
VARCHAR(20) → VARCHAR(30)
计算方式:
def targetLength(sourceLength: Int): Int =
math.ceil(sourceLength * 1.5).toInt
需要注意,1.5 只是针对常见中文数据的经验值,并不是所有数据都适用。
如果数据中包含生僻字、Emoji 或其他多语言字符,更稳妥的方式是在建表前根据实际数据计算其在目标字符集下的最大字节长度。
如果目标表由用户提前创建,则平台不应自动修改表结构。
这种情况下,Spark 批量写入失败后,可以进一步定位具体是哪一行、哪个字段超长,并给出明确的字段调整建议。
处理流程可以设计为:
批量写入失败
↓
事务回滚
↓
对当前批次逐条重试
↓
定位失败数据
↓
定位失败字段
↓
计算该字段在目标字符集下的实际长度
↓
给出字段扩容建议
例如目标字段为:
CUSTOMER_NAME VARCHAR(20)
某条数据转换成 UTF-8 后实际需要 30 字节,则可以输出:
字段 CUSTOMER_NAME 写入失败。
目标字段长度:20 字节
当前数据需要:30 字节
长度不足:10 字节
建议将字段调整为 VARCHAR(30) 或更大。
Spark 中可以直接根据目标字符集计算实际字节长度:
def requiredLength(
value: String,
targetCharset: Charset
): Int = {
value.getBytes(targetCharset).length
}
不足长度可以直接计算:
def missingLength(required: Int, declared: Int): Int =
math.max(0, required - declared)
如果目标数据库配置为按字符数计算长度,则不应再比较字节数。
例如:
val characters =
value.codePointCount(0, value.length)
此时应比较:
实际字符数
与:
目标 VARCHAR 定义长度
而不是比较 UTF-8 编码后的字节数。
Spark 在 DM8 数据库之间进行数据迁移时,不能只比较源表和目标表的 VARCHAR 定义长度,还需要同时考虑:
源数据库字符集
目标数据库字符集
字段长度语义
实际数据内容
对于平台实现,可以分成两种处理方式:
这样既可以避免因为字符集转换导致迁移失败,也可以把数据库返回的模糊异常转换成用户能够直接处理的字段修改建议。
SF_GET_UNICODE_FLAGLENGTH_IN_CHAR文章
阅读量
获赞
