将UTF8表中的latin1字符转换为UTF8

60

今天我才意识到我的 PHP 脚本中缺少了这个功能:

mysql_set_charset('utf8');

我所有的表都是InnoDB,校对规则为"utf8_unicode_ci",所有的VARCHAR列也是"utf8_unicode_ci"。我的PHP脚本上有mb_internal_encoding('UTF-8');,并且我所有的PHP文件都以UTF-8编码。

因此,到目前为止,每当我插入一些带有变音符号的内容,例如:

mysql_query('INSERT INTO `table` SET `name`="Jáuò Iñe"');

在这种情况下,“name”内容将是:Jáuò Iñe

由于我已经修复了PHP和MySQL之间的字符集,新的INSERT现在可以正确存储。然而,我想修复所有旧的行目前都“混乱”了。我已经尝试了很多方法,但它总是在第一个“非法”字符处中断字符串。这是我的当前代码:

$m = mysql_real_escape_string('¿<?php echo "¬<b>\'PHP &aacute; (á)ţăriîş </b>"; ?> ă-ţi abcdd;//;ñç´พดแทฝใจคçăâξβψδπλξξςαยนñ ;');
mysql_set_charset('utf8');
mysql_query('INSERT INTO `table` SET `name`="'.$m.'"');
mysql_set_charset('latin1');
mysql_query('INSERT INTO `table` SET `name`="'.$m.'"');
mysql_set_charset('utf8');

$result = mysql_iquery('SELECT * FROM `table`');
while ($row = mysql_fetch_assoc($result)) {
    $message = $row['name'];
    $message = mb_convert_encoding($message, 'ISO-8859-15', 'UTF-8');
    //$message = iconv("UTF-8", "ISO-8859-1//IGNORE", $message);
    mysql_iquery('UPDATE `table` SET `name`="'.mysql_real_escape_string($message).'" WHERE `a1`="'.$row['a1'].'"');
}

除了字符“ă”及其后面的字符未被包括在字符串中外,它按预期字符进行“UPDATE”。我是指该字符和其后的字符没有被包括在字符串中。

此外,使用代码中注释的"iconv()"进行测试也会出现同样的情况,即使使用//IGNORE和//TRANSLIT。

我还测试了几种字符集,包括ISO-8859-1和ISO-8859-15。


你的 Jáuò Iñe 应该是 Jáuò Iñe,对吗?两步 ALTER 可能是适当的(而且相对简单)。请参见 https://dev59.com/0rn4oIgBc1ULPQZFPQY-#71960959。 - Rick James
Rick - 我很感激你的帮助,但是这个问题已经在2012年被问过了,当时我得到了一个解答,它拯救了我的一天(我仍然非常感激!),你在2020年编辑了我的问题,而两年后你又提出了一个答案。这真的很有趣 :D :-) -- 祝你有美好的一天,享受你的日子! - Nuno
4个回答

155

根据你的描述,看起来你有一些UTF-8格式的数据最初是以Latin-1格式存储的,然后在转换为UTF-8时转换不正确。这些数据是可以恢复的;你需要使用MySQL函数,比如

convert(cast(convert(name using  latin1) as binary) using utf8)

取决于数据在编码转换期间如何被更改,可能需要省略内部转换。


11
哇,你救了我的一天!我以前从未使用过这些函数,在更新时现在用了它们,它起作用了。非常感谢你! - Nuno
2
谢谢!我已经编写了一个小的PHP脚本,循环遍历每个表中的所有列。解决了问题 :) - wiesson
1
超过5年后,这个好答案让我避免了很多烦恼。你真是太聪明了!不过,我有一个问题:如果要转换的列很多,用通配符代替名称可以吗? - Stephen Adelakun
1
注意:utf8混乱的原因有很多。该表达式仅适用于一种情况。以下是我已经确定的情况列表:http://mysql.rjweb.org/doc.php/charcoll#fixes_for_various_cases - Rick James
1
注意:不要在已经是UTF8的数据上运行上述函数。它会“清除”第一个已经是UTF8字符后面的所有内容。您可以通过两次在同一字段上运行它来证明这一点。 - UncaAlby
显示剩余9条评论

35

我搜索了一两个小时后找到了这个答案,我需要将一个旧的tt_news数据库从typo迁移到新版本的typo3。我已经尝试将导出文件中的字符集转换并重新导入,但无法使其正常工作。

然后我尝试了上面ABS的答案,并在表格上启动了更新:

UPDATE tt_news SET 
    title=convert(cast(convert(title using  latin1) as binary) using utf8), 
    short=convert(cast(convert(short using  latin1) as binary) using utf8), 
    bodytext=convert(cast(convert(bodytext using  latin1) as binary) using utf8)
WHERE 1
你还可以转换图像标题、图像替代文本、图像标题文本和关键字,如果需要的话。希望这能帮助那些将tt_news迁移到新的typo3版本的人。

2
直到我看到你的回答,我才收到ABS的答复。 - Cedric Ipkiss
确实,ABS的答案是这个问题的“关键”。Marcel的答案在一个完整的例子中使用了这个“关键”。请给两个答案点赞! - Jerry Krinock
两个都点赞了,对于任何跨数据库的导入/导出都是至关重要的查询。拯救了我的一天! - Moseleyi

0

方法更好

使用正常的数据库连接方式

然后使用此代码来实现您需要的功能

您必须通过HTML头部中的meta标签将页面编码设置为utf-8(不要忘记这一点)

然后使用此代码

    $result = mysql_query('SELECT * FROM shops');
    while ($row = mysql_fetch_assoc($ 
    $name= iconv("windows-1256", "UTF-8", $row['name']);

   mysql_query("SET NAMES 'utf8'"); 
   mysql_query("update   `shops` SET `name`='".$name."'  where ID='$row[ID]'  ");
    }

这并不更好,而且你的方法在名称中包含撇号时会出错。另外,为什么要为每一行执行“SET NAMES”? - Lightness Races in Orbit

0
我强烈建议使用'utf8mb4'而不是'utf8',因为utf8无法存储一些中文字符和表情符号。

3
确实,但这并不是回答问题的答案。 - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接