1. 问题现象
Spark 任务写数据到 MySQL 时,如果字段中包含 emoji 字符,insert 会失败。
草稿中的描述是:
spark 写数据到 mysql 包含 emoji 字符导致 insert 失败,
已经将 mysql 的 db、table、column 全部改成了 utf8mb4 还是不行。
这类问题常见异常一般是:
java.sql.SQLException: Incorrect string value
或者:
Incorrect string value: '\xF0\x9F\x98\x84...' for column 'xxx'
\xF0\x9F... 这种字节序列通常就是 emoji 这类 4 字节 UTF-8 字符。
2. 为什么 utf8 不够用
MySQL 里历史上的 utf8 不是完整 UTF-8,它最多只能存 3 字节字符。emoji、部分生僻字、一些特殊符号需要 4 字节 UTF-8 才能表示。
MySQL 提供的完整 4 字节 UTF-8 字符集是:
utf8mb4
所以如果要存 emoji,至少要保证:
- database 是
utf8mb4 - table 是
utf8mb4 - column 是
utf8mb4 - JDBC 连接会话也是
utf8mb4
很多时候问题就出在第四点:库、表、字段都改了,但连接会话还是 utf8 或 latin1。
3. 如何确认问题
先看表字段定义:
SHOW FULL COLUMNS FROM table_name;
重点看 Collation:
utf8mb4_general_ci
utf8mb4_unicode_ci
utf8mb4_0900_ai_ci
再看表默认字符集:
SHOW CREATE TABLE table_name;
如果看到:
DEFAULT CHARSET=utf8mb4
说明表层面是对的。
然后看当前连接会话字符集:
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
重点关注:
character_set_client
character_set_connection
character_set_results
collation_connection
如果这里还是:
utf8
latin1
即使表字段是 utf8mb4,通过这个连接写入 emoji 仍然可能失败。
4. Spark JDBC 写 MySQL 时容易忽略的地方
Spark 写 MySQL 通常是这样:
df.write
.format("jdbc")
.option("url", "jdbc:mysql://mysql-host:3306/test")
.option("dbtable", "table_name")
.option("user", "user")
.option("password", "password")
.mode("append")
.save()
如果 JDBC URL 里没有明确字符集,或者使用了老版本 MySQL Connector/J,就可能出现连接字符集和表字符集不一致。
可以把 URL 调整为:
jdbc:mysql://mysql-host:3306/test?useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci
如果是 MySQL 8,可以使用:
jdbc:mysql://mysql-host:3306/test?useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8mb4_0900_ai_ci
注意这里的 characterEncoding 写的是 Java 侧字符集名称 UTF-8,不是 MySQL 字符集名称 utf8mb4。utf8mb4 更适合通过 MySQL 的 charset/collation 配置来控制。
5. 解决方案
5.1 修改库字符集
ALTER DATABASE db_name
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
5.2 修改表字符集
ALTER TABLE table_name
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
5.3 确认字段字符集
如果只改表默认字符集,但字段是历史字段,有时字段自身仍然不是 utf8mb4。
可以单独修改字段:
ALTER TABLE table_name
MODIFY content VARCHAR(1000)
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
5.4 修改 JDBC URL
Spark 写入时配置:
val url =
"jdbc:mysql://mysql-host:3306/db_name" +
"?useUnicode=true" +
"&characterEncoding=UTF-8" +
"&connectionCollation=utf8mb4_unicode_ci" +
"&useSSL=false"
df.write
.format("jdbc")
.option("url", url)
.option("dbtable", "table_name")
.option("user", "user")
.option("password", "password")
.mode("append")
.save()
5.5 升级 MySQL Connector/J
如果项目使用的是很老的 MySQL 驱动,也建议升级。
Maven 依赖示例:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
老版本驱动对字符集、时区、SSL、认证方式的处理都容易遇到兼容性问题。
6. 应急处理方式
草稿里提到最后“改掉了 emoji”,这是一个可用但不完整的应急方案。
如果业务允许,可以在 Spark 写入前清洗 emoji:
import org.apache.spark.sql.functions.regexp_replace
val cleanedDf = df.withColumn(
"content",
regexp_replace(col("content"), "[\\x{10000}-\\x{10FFFF}]", "")
)
但是这种方式会丢失原始文本信息。如果字段用于用户评论、昵称、消息内容,一般不建议直接删除 emoji。更好的方式还是修正 MySQL 字符集和 JDBC 连接。
7. 排查顺序
建议按下面顺序排查:
| 步骤 | 命令 | 目的 |
|---|---|---|
| 1 | SHOW FULL COLUMNS FROM table_name |
确认字段 collation |
| 2 | SHOW CREATE TABLE table_name |
确认表默认字符集 |
| 3 | SHOW VARIABLES LIKE 'character_set%' |
确认连接会话字符集 |
| 4 | 检查 JDBC URL | 确认连接端没有使用错误字符集 |
| 5 | 检查驱动版本 | 排除老驱动兼容问题 |
| 6 | 单独插入 emoji 测试 | 确认 MySQL 是否能直接写入 |
测试 SQL:
INSERT INTO table_name(content) VALUES ('hello 😄');
如果 MySQL 客户端能写,Spark 不能写,重点排查 Spark JDBC URL 和驱动版本。
如果 MySQL 客户端也不能写,重点排查库、表、字段、连接会话字符集。
8. 总结
Spark 写 MySQL 遇到 emoji 失败,本质上通常不是 Spark 写入逻辑问题,而是 MySQL 字符集链路没有完整切到 utf8mb4。
只改下面这些还不一定够:
database
table
column
还要检查:
JDBC URL
MySQL Connector/J 版本
character_set_client
character_set_connection
character_set_results
collation_connection
如果业务允许,删除 emoji 可以作为临时方案;如果要保留原始文本,就应该把 MySQL 和 JDBC 连接完整改成支持 utf8mb4 的链路。