Perl写入MariaDB时decode_entities解码变音符号报编码错误
问题成因
- Perl的字符串分两类:带UTF-8内部标记的Unicode字符串、无标记的原始字节串。你给DBI连接配置了
mysql_enable_utf8mb4 => 1之后,DBD::mysql只会正确处理前者,会自动按utf8mb4编码转换后传给数据库;传入无标记的字节串时,驱动不会做任何编码转换,直接把原始字节发给数据库。 - 脚本里直接写
'Ärztin'能正常插入,是因为你的Perl脚本本身以UTF-8编码保存,Perl解析源码时会自动给这类字面量打上UTF-8标记,完全符合驱动的输入要求。 HTML::Entities的decode_entities默认返回ISO-8859-1(Latin-1)编码的原始字节串,不带UTF-8标记:比如ä解码后是单字节0xE4,和你报错信息里的\xE4rztin完全对应。这类单字节序列传给要求utf8mb4编码的列,自然触发非法字符串值报错。- 终端打印、写文件能正常显示,是因为你的终端默认编码兼容Latin-1,单字节变音符号刚好能被正确渲染,和字符串是否带UTF-8标记没有关系。
解决方案
优先选第一种改法,改动最小逻辑最顺:
- 配置
decode_entities的实体处理回调,直接返回带UTF-8标记的Unicode字符串:
处理后的$name和你脚本里写死的字符串形态完全一致,直接传入use Encode qw(decode); # 所有解码出的字符统一转为UTF-8标记的Perl内部字符串 $name = decode_entities($name, sub { decode('UTF-8', chr($_[0]), Encode::FB_CROAK) });execute即可正常插入。 - 如果已经按默认方式调用了
decode_entities拿到Latin-1字节串,手动转码即可:use Encode qw(decode); $name = decode_entities($name); # 把Latin-1编码的原始字节串解码为带UTF-8标记的Unicode字符串 $name = decode('ISO-8859-1', $name, Encode::FB_CROAK);
注意事项
- 不要直接用
utf8::upgrade或者手动给字符串强制打UTF-8标记,会把Latin-1单字节错按UTF-8解析,最终存入数据库的会是乱码。 - 调试时可以用
utf8::is_utf8($name)确认字符串状态,只要传入execute的参数是带UTF-8标记的字符串,就不会再出现这类编码报错。
内容的提问来源于stack exchange,提问作者giordano
相关产品推荐
相关产品推荐

