【问题标题】:MySQL: data being mangled while changing column to UTF8MySQL:将列更改为 UTF8 时数据被破坏
【发布时间】:2019-12-19 17:29:08
【问题描述】:

我正在将在 LATIN1 中创建的 MySQL 数据库迁移到 UTF8。为此,我首先将每一列更改为相应的二进制类型,然后更改为 UTF8:

ALTER TABLE clientes CHARACTER SET utf8;
ALTER TABLE clientes change nombre nombre varbinary(255);
ALTER TABLE clientes change nombre nombre varchar(255) character set utf8;

因为,根据所有文档,这是防止数据被破坏的正确方法...

...但是,数据仍然被破坏。我只举两个例子:

  • 单词“Larrasoaña”在“ñ”处被截断,出现以下错误:警告:#1366 字符串值不正确:“nombre”列的“\xF1a”
  • 单词“Jesús y María”在“ú”处被截断,出现错误警告:#1366 字符串值不正确:'\xFAs y M...' for column 'nombre'

这些数据是如何进入的?好吧,数据库是 PHP Web 应用程序的后端,它使用 UTF8 处理一切(包括使用 "SET NAMES UTF8" 连接到 MySQL 服务器)......除了正确创建数据库。所以我假设添加的所有数据都是 UTF8 格式的。

总结一下:我似乎将 UTF8 文本存储在 LATIN1 列中,现在我尝试将列更改为 UTF8,文本被截断。

为什么会这样?我能做什么?

编辑:忘了提,我是从 PhpMyAdmin 做这一切的,因为我没有命令行访问权限。

【问题讨论】:

    标签: mysql utf-8


    【解决方案1】:

    F1 和 FA 是 latin1 编码。你需要告诉 MySQL 数据是latin1。一种方法是通过SET NAMES latin1

    但请注意...这与您尝试将数据存储到的列的设置独立。而且,现在,utf8mb4 是文本的首选设置。 MySQL 将在列的编码和客户端的编码之间进行转换。但是你必须通过连接参数(或SET NAMES)告诉它客户端的编码。

    ALTER TABLEs适用于某些情况,而不是所有情况!您可能想要http://mysql.rjweb.org/doc.php/charcoll#fixes_for_various_cases 中的第一个条目

    表是 CHARACTER SET latin1 并在 latin1 中正确编码;想 utf8mb4:

    ALTER TABLE tbl CONVERT TO CHARACTER SET utf8mb4;
    

    我不知道您的数据是否已被不可挽回地占用。请提供其中一行以及 HEX。

    十六进制

    “Larrasoaña”编码为 4C61727261736F61F161,“Jesús y María”编码为 4A6573FA732079204D6172ED6120

    这些是 latin1 编码的(或 latin5 或 dec8)。如果表定义 (SHOW CREATE TABLE) 显示 latin1,那么您可以不理会它。 (latin1 处理西欧语言,但不处理亚洲语言。)

    如果要将所有文本列转换为 utf8 或 utf8mb4,请像我上面介绍的那样执行 ALTER。您的 3-Alter 方法将正常工作;它假定 latin1 列中的字节实际上是 UTF-8 字节(它们不是)。

    但是...您必须根据客户的需要指定客户的编码。 客户端和桌子是否同意并不重要,因为将提供转换。

    为什么 3 步改变失败

    ALTER TABLE clientes CHARACTER SET utf8; -- 这会为 new 列设置 default 字符集。它对现有的列定义和这些列中的任何数据没有影响。

    ALTER TABLE clientes change nombre nombre varbinary(255); -- 这表示“忘记任何文本编码”。那就是 F1 现在只是一堆位,而不是 ñ 的 latin1 表示。

    ALTER TABLE clientes change nombre nombre varchar(255) character set utf8; -- 这需要那些 varbinary 位并说“让我们将它们视为utf8。这会给出错误消息,因为 F1 不是 utf8 的有效编码。

    该过程是合适的如果字节是已经 utf8字节。也就是说,如果它已经是 ñ 的 2 字节 C3B1。 (顺便说一下,这通常表现为“Mojibake”,当解释为 latin1 时显示为 ñ。)

    1-Alter 过程...

    ALTER TABLE clientes CONVERT TO CHARACTER SET utf8;(转换整个表)或ALTER TABLE clientes MODIFY nombre varchar(255) character set utf8;(仅转换一列)。他们做以下事情:

    对于每个文本(char/varchar/text)列,它根据其当前编码(latin1,F1)读取数据,将其转换为 utf8(或 utf8mb4)(C3B1)并写回该行。同时将声明改为CHARACTER SET utf8

    也就是说,在不更改“文本”的情况下更改CHARACTER SET 是“正确的”过程。确实,编码发生了变化(F1 -> C3B1),但这与CHARACTER SET的变化一致。

    恢复

    您的前 2 个 ALTER 有效,对吗?第三个是成功了、失败了还是留下了一张乱七八糟的桌子?

    如果它中止了,留下varbinary,然后再做2个改变:首先回到latin1;然后直接转utf8。

    如果它给你留下了一个混乱的列,特别是如果行被截断,那么你需要返回备份,或者重新加载数据。

    【讨论】:

    • 嗯,我有备份,它是数据库的测试副本(不是生产站点);-),所以没有任何“不可修复”的东西。无论如何,我从备份中恢复并再次尝试,没有转换为二进制;我只是把“ALTER TABLE clientes change nombre nombre varchar(255) character set utf8;”命令......它似乎奏效了:没有错误,文本似乎没问题。还有什么我应该做的吗?我没有使用“ALTER TABLE ... CONVERT TO CHARACTER SET utf8”,因为我不知道是在更改列类型之前还是之后执行它。
    • 至于使用“SET NAMES”,我在托管站点上使用 PhpMyAdmin(我无法控制 PhpMyAdmin 的安装),所以看起来很困难。有没有办法从托管的 PhpMyAdmin 做到这一点?
    • @PaulJ - 如果您使用的是LOAD DATA,它有一个CHARACTER SET 子句。或者您正在使用 PhpMyAdmin 的其他功能?
    • 我不明白。数据已经在数据库中;我不需要使用加载数据。我恢复数据库的方式是对整个数据库进行核对,然后加载我使用 PhpMyAdmin 中的“导入”选项所做的 Mysqldump 备份。通过这种方式,我得到了带有 LATIN1 列的原始数据库,并按照上面评论中的说明对它们进行了修改。
    • @PaulJ - 我在答案中添加了一堆。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 2021-02-13
    • 1970-01-01
    • 2015-04-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多