【问题标题】:How to detect UTF-8 characters in a Latin1 encoded column - MySQL如何在 Latin1 编码列中检测 UTF-8 字符 - MySQL
【发布时间】:2012-03-07 10:48:01
【问题描述】:

我即将承担将数据库从 Latin1 转换为 UTF-8 的繁琐且充满难题的任务。

此时我只想检查我的表中存储了哪些类型的数据,因为这将决定我应该使用哪种方法来转换数据。

具体来说,我想检查 Latin1 列中是否有 UTF-8 字符,最好的方法是什么?如果只有几行受到影响,那么我可以手动修复它。

选项 1. 执行 MySQL 转储并使用 Perl 搜索 UTF-8 字符?

选项 2. 使用 MySQL CHAR_LENGTH 查找具有多字节字符的行? 例如SELECT name FROM clients WHERE LENGTH(name) != CHAR_LENGTH(name); 够了吗?

目前我已将 Mysql 客户端编码切换为 UTF-8。

【问题讨论】:

  • 根据定义,您不能将 UTF-8 数据存储在 Latin1 列中。愿意为您的问题提供更多背景信息吗?
  • @deceze 您可能会无意中将 UTF-8 数据存储在 LATIN1 列中,因为 LATIN1 是 8 位字符集。它最终看起来像错误编码的一团糟。
  • @Borealid 0xF0 0x53 不是有效的 UTF8 序列,这就是为什么这在一定程度上是可行的。
  • @triplee 诅咒,应该选择 30,000 多个模棱两可的两字节序列之一!但我想你明白我的意思。有一些字符不能是 UTF-8,但是很多 latin1 二字符序列也是有效的 UTF-8 二字节字符。
  • @tripleee 还有字节序列 0xC2A0,在 UTF-8 中是一个不间断空格,在 latin1 中是一个后跟一个不间断空格?奇妙的消失角色!

标签: mysql utf-8 character-encoding latin1


【解决方案1】:

字符编码,如时区,是问题的源头。

您可以做的是查找任何“高位 ASCII”字符,因为这些字符要么是 LATIN1 重音字符或符号,要么是 UTF-8 多字节字符的第一个字符。除非你稍微作弊,否则分辨出其中的差别并不容易。

要弄清楚哪种编码是正确的,您只需SELECT 两个不同的版本并进行视觉比较。这是一个例子:

SELECT CONVERT(CONVERT(name USING BINARY) USING latin1) AS latin1, 
       CONVERT(CONVERT(name USING BINARY) USING utf8) AS utf8 
FROM users 
WHERE CONVERT(name USING BINARY) RLIKE CONCAT('[', UNHEX('80'), '-', UNHEX('FF'), ']')

这变得异常复杂,因为 MySQL 正则表达式引擎似乎忽略了诸如 \x80 之类的内容,因此有必要改用 UNHEX() 方法。

这会产生如下结果:

latin1                utf8
----------------------------------------
Björn                Björn

【讨论】:

  • 对于迟到的回复和模糊的初始问题表示歉意。授予此答案是因为它或多或少地帮助我 检测 可能意图是 UTF8 字符的字符。赞成 deceze 的答案,因为它包含我在数据库其他地方的情况
  • 太棒了——这个小块帮助我解决了一个问题,即 utf8 编码的数据被插入到 utf8 表中,但被解释为 latin1,因为我是通过 mysql CLI 输入的......虽然很有趣,因为因为系统设置为 UTF8 它在输入和选择时看起来很好(只是在相关网站上被解码和呈现时)。
  • 有时,如果您从具有完全相同的错误配置的两个连接读取和写入数据,它会神奇地起作用。有时,两个错误确实是正确的。
  • 在 ASCII 范围之上的 UTF-8 编码代码点的第一个字节在 0xC2-0xF4 范围内(U+0080 以字节 0xC2 开始;U+10FFFF 以 0xF4 开始)。所以这个答案的范围可能会更严格,以减少误报。
  • 这也会得到假阳性结果 => latin1 "é" 字母(utf char : 195, ansi char : 233 ),有没有解决方法?
【解决方案2】:

由于您的问题并不完全清楚,让我们假设一些场景:

  1. 迄今为止错误的连接:您一直使用 latin1 编码错误地连接到您的数据库,但在数据库中存储了 UTF-8 数据(在这种情况下,列的编码无关紧要)。这就是我描述的here 的情况。在这种情况下,很容易解决:通过 latin1 连接将数据库内容转储到文件中。这会将错误存储的数据转换为错误正确存储的 UTF-8,到目前为止它的工作方式(阅读前面链接的文章了解血腥细节)。然后,您可以通过正确设置的 utf8 连接将数据重新导入数据库,并按应有的方式存储。
  2. 迄今为止错误的列编码: UTF-8 数据通过 utf8 连接插入到 latin1 列中。在那种情况下算了,数据就没了。任何非 latin1 字符都应替换为 ?
  3. 迄今为止一切都很好,此后添加了对 UTF-8 的支持: 您已将 Latin-1 数据正确存储在 latin1 列中,通过 latin1 连接插入,但希望扩展该数据以允许使用 UTF-8 数据.在这种情况下,只需将列编码更改为 utf8。 MySQL 将为您转换现有数据。然后只需确保在插入 UTF-8 数据时将数据库连接设置为 utf8。

【讨论】:

  • 如果多个客户端一直在添加数据,并且其中一些认为他们应该提交 utf8,那么您将得到一个基本上需要手动整理的邪恶组合。这并不意味着您不能自动化部分流程,事实上,大多数情况可能无需人工干预即可决定。
  • 是的,但是你真的完全被foobar'd了。在尝试回答这种情况之前,OP 需要提供更多关于手头实际问题的信息。
  • 对于案例 1,对我有用的命令是 mysqldump --default-character-set=latin1 -u user -p database。然后我不得不进入转储文件并将SET NAMES latin1更改为utf8。然后重新导入转储文件并全部修复。
【解决方案3】:

a script on github 可以帮助解决这类事情。

【讨论】:

  • 这个脚本对我来说非常好用,我贡献了一些改进让它更快更灵活。我还有 a branch 转换为 MySQL 的 'proper' utf8mb4 charset
  • 这个脚本有效.. 仍然不明白它是如何工作的.. 需要在某个时候完成它.. 从 latin1 - utf8 几乎毫无痛苦地移动,必须添加 mysql_set_charset("utf8"); 以便 php 正确使用它之后。
  • OP 询问如何在 Latin1 列中检测 UTF-8 字符。 AFAICT,mysql-convert-latin1-to-utf8 脚本,目前实际上并没有帮助。相反,它有一个用户可修改的 $collationMap 数组,指定一组 key-value 对排序规则。对于排序规则与其中一个 key 匹配的每一列,脚本将盲目地假定其内容是使用与 value 的排序规则相对应的字符集编码的。该脚本将列的排序规则(以及隐含的字符集)更改为后者,同时保留内容的二进制值。
  • 嗯,检测是不可能的。有人把一个方形钉子塞进一个圆孔里,问为什么它出来的时候不是方形的……你可以说出来,因为出来的数据看起来不太对,但你必须看看它才能知道(或识别在解释为 UTF-8 时比拉丁字符序列更有可能的常见字符序列)。
【解决方案4】:

我将为所有有效的 UTF8 序列创建数据库和 grep 转储。从那里拿它取决于你得到什么。 SO上有多个关于识别无效UTF8的问题;你基本上可以颠倒逻辑。

编辑:所以基本上,任何完全由 7 位 ASCII 组成的字段都是安全的,任何包含无效 UTF-8 序列的字段都可以假定为 Latin-1。应该检查剩余的数据 - 如果幸运的话,少数明显的替换将解决绝对多数(将 ö 替换为 Latin-1 ö 等)。

【讨论】:

  • This answer 包含相当长的可能错误组合列表。
猜你喜欢
  • 2020-09-15
  • 2014-05-17
  • 2012-03-24
  • 2017-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多