mysql编码地狱

dfuffjeb  于 2021-10-10  发布在  Java
关注(0)|答案(2)|浏览(400)

我有一个网站已经运行了将近20年,不幸的是我犯了一个错误,没有将html字符集与mysql字符集对齐,所以我所有的数据似乎都是双重编码的(我想),或者可能是Mojibake,或者两者都是。也许你们中的一位Maven能帮我澄清一下。
在我继续之前,您应该知道我打算升级到带有utf8字符和表情符号的tomcat 9 html5

With page pageEncoding="UTF-8"  at the top of each page
request CharacterEncoding set to "UTF-8"
response CharacterEncoding set to "UTF-8"
and ContentType set to "text/html; charset=utf-8"

新的mysql数据库版本8(最新版本)已设置并位于同一台机器上。包含所有记录的当前(实时)mysql版本是版本5.6.19。
这是我在workbench中看到的一小组记录
这是上表的设置:
创建表 test ( id int(11)非空自动增量, txt varchar(255)collate utf8\u unicode\u ci默认为空,主键( id ))引擎=innodb自动增量=19默认字符集=utf8 collate=utf8\U unicode\U ci;
mysql 5.6变量
所有这些目前都完美地呈现在网页上。下面是在页面上呈现上述无意义数据及其字节数组表示形式。。。

REC = don’t go breaking my heart😛
    Bytes: 64 6f 6e ffffffe2 ffffff80 ffffff99 74 20 67 6f 20 62 72 65 61 6b 69 6e 67 20 6d 79 20 68 65 61 72 74 fffffff0 ffffff9f ffffff98 ffffff9b

    REC = 😍
    Bytes: fffffff0 ffffff9f ffffff98 ffffff8d 20

    REC = Haha...... 🤤🤤🤤
    Bytes: 48 61 68 61 2e 2e 2e 2e 2e 2e 20 fffffff0 ffffff9f ffffffa4 ffffffa4 fffffff0 ffffff9f ffffffa4 ffffffa4 fffffff0 ffffff9f ffffffa4 ffffffa4

    REC = Mitteleuropäische Normalzeit
    Bytes: 4d 69 74 74 65 6c 65 75 72 6f 70 ffffffc3 ffffffa4 69 73 63 68 65 20 4e 6f 72 6d 61 6c 7a 65 69 74

    REC = Středoevropský letní čas
    Bytes: 53 74 ffffffc5 ffffff99 65 64 6f 65 76 72 6f 70 73 6b ffffffc3 ffffffbd 20 6c 65 74 6e ffffffc3 ffffffad 20 ffffffc4 ffffff8d 61 73

    REC = 中国标准时间
    Bytes: ffffffe4 ffffffb8 ffffffad ffffffe5 ffffff9b ffffffbd ffffffe6 ffffffa0 ffffff87 ffffffe5 ffffff87 ffffff86 ffffffe6 ffffff97 ffffffb6 ffffffe9 ffffff97 ffffffb4

    REC = Центральная Европа летнее время
    Bytes: ffffffd0 ffffffa6 ffffffd0 ffffffb5 ffffffd0 ffffffbd ffffffd1 ffffff82 ffffffd1 ffffff80 ffffffd0 ffffffb0 ffffffd0 ffffffbb ffffffd1 ffffff8c ffffffd0 ffffffbd ffffffd0 ffffffb0 ffffffd1 ffffff8f 20 ffffffd0 ffffff95 ffffffd0 ffffffb2 ffffffd1 ffffff80 ffffffd0 ffffffbe ffffffd0 ffffffbf ffffffd0 ffffffb0 20 20 ffffffd0 ffffffbb ffffffd0 ffffffb5 ffffffd1 ffffff82 ffffffd0 ffffffbd ffffffd0 ffffffb5 ffffffd0 ffffffb5 20 ffffffd0 ffffffb2 ffffffd1 ffffff80 ffffffd0 ffffffb5 ffffffd0 ffffffbc ffffffd1 ffffff8f

    REC = Иркутск стандартное время
    Bytes: ffffffd0 ffffff98 ffffffd1 ffffff80 ffffffd0 ffffffba ffffffd1 ffffff83 ffffffd1 ffffff82 ffffffd1 ffffff81 ffffffd0 ffffffba 20 20 ffffffd1 ffffff81 ffffffd1 ffffff82 ffffffd0 ffffffb0 ffffffd0 ffffffbd ffffffd0 ffffffb4 ffffffd0 ffffffb0 ffffffd1 ffffff80 ffffffd1 ffffff82 ffffffd0 ffffffbd ffffffd0 ffffffbe ffffffd0 ffffffb5 20 ffffffd0 ffffffb2 ffffffd1 ffffff80 ffffffd0 ffffffb5 ffffffd0 ffffffbc ffffffd1 ffffff8f

    REC = heure d’été d’Europe centrale
    Bytes: 68 65 75 72 65 20 64 ffffffe2 ffffff80 ffffff99 ffffffc3 ffffffa9 74 ffffffc3 ffffffa9 20 64 ffffffe2 ffffff80 ffffff99 45 75 72 6f 70 65 20 63 65 6e 74 72 61 6c 65

    REC = توقيت برازيليا الرسمي
    Bytes: ffffffd8 ffffffaa ffffffd9 ffffff88 ffffffd9 ffffff82 ffffffd9 ffffff8a ffffffd8 ffffffaa 20 ffffffd8 ffffffa8 ffffffd8 ffffffb1 ffffffd8 ffffffa7 ffffffd8 ffffffb2 ffffffd9 ffffff8a ffffffd9 ffffff84 ffffffd9 ffffff8a ffffffd8 ffffffa7 20 ffffffd8 ffffffa7 ffffffd9 ffffff84 ffffffd8 ffffffb1 ffffffd8 ffffffb3 ffffffd9 ffffff85 ffffffd9 ffffff8a

    REC = เวลาอินโดจีน
    Bytes: ffffffe0 ffffffb9 ffffff80 ffffffe0 ffffffb8 ffffffa7 ffffffe0 ffffffb8 ffffffa5 ffffffe0 ffffffb8 ffffffb2 ffffffe0 ffffffb8 ffffffad ffffffe0 ffffffb8 ffffffb4 ffffffe0 ffffffb8 ffffff99 ffffffe0 ffffffb9 ffffff82 ffffffe0 ffffffb8 ffffff94 ffffffe0 ffffffb8 ffffff88 ffffffe0 ffffffb8 ffffffb5 ffffffe0 ffffffb8 ffffff99

    REC = heure normale d’Afrique de l’Ouest
    Bytes: 68 65 75 72 65 20 6e 6f 72 6d 61 6c 65 20 64 ffffffe2 ffffff80 ffffff99 41 66 72 69 71 75 65 20 64 65 20 6c ffffffe2 ffffff80 ffffff99 4f 75 65 73 74

    REC = Центральная Америка летнее время
    Bytes: ffffffd0 ffffffa6 ffffffd0 ffffffb5 ffffffd0 ffffffbd ffffffd1 ffffff82 ffffffd1 ffffff80 ffffffd0 ffffffb0 ffffffd0 ffffffbb ffffffd1 ffffff8c ffffffd0 ffffffbd ffffffd0 ffffffb0 ffffffd1 ffffff8f 20 ffffffd0 ffffff90 ffffffd0 ffffffbc ffffffd0 ffffffb5 ffffffd1 ffffff80 ffffffd0 ffffffb8 ffffffd0 ffffffba ffffffd0 ffffffb0 20 20 ffffffd0 ffffffbb ffffffd0 ffffffb5 ffffffd1 ffffff82 ffffffd0 ffffffbd ffffffd0 ffffffb5 ffffffd0 ffffffb5 20 ffffffd0 ffffffb2 ffffffd1 ffffff80 ffffffd0 ffffffb5 ffffffd0 ffffffbc ffffffd1 ffffff8f

    REC = Ora de vară a Europei de Est
    Bytes: 4f 72 61 20 64 65 20 76 61 72 ffffffc4 ffffff83 20 61 20 45 75 72 6f 70 65 69 20 64 65 20 45 73 74

如您所见,数据是可读的。。。在utf8网页和旧的windows-1250网页上。。。但是其他任何东西都读不懂,我已经用谷歌搜索到了这个,并且尝试了我在各个网站上找到的所有信息。是的,我看过里克·詹姆斯的文件,但他的建议对我都不管用。
我已经尝试了转换和强制转换的所有组合,我能想到最流行的一个例子:从测试中选择转换(转换(转换(使用拉丁语转换为二进制)使用utf8)作为res;
它成功地解码了“mitteleuropäische normalzeit”,但所有其他记录都变为空。
我还尝试了java的解码器byte[]utf8bytes=rec.getstring(“txt”).getbytes(“iso-8859-1”);字符串结果=新字符串(utf8bytes,“utf-8”);
结果在网页上显示良好,但若我将结果写入文件或将结果发送到新数据库,它将再次变成垃圾。

don’t go breaking my heart😛
😠
Haha...... 🤤🤤🤤
Mitteleuropäische Normalzeit
StÅ™edoevropský letní Äas
中国标准时间
Ð¦ÐµÐ½Ñ‚Ñ€Ð°Ð»ÑŒÐ½Ð°Ñ Ð•Ð²Ñ€Ð¾Ð¿Ð°  летнее времÑ
ИркутÑк  Ñтандартное времÑ
heure d’été d’Europe centrale
توقيت برازيليا الرسمي
เวลาอินโดจีน
heure normale d’Afrique de l’Ouest
Ð¦ÐµÐ½Ñ‚Ñ€Ð°Ð»ÑŒÐ½Ð°Ñ Ðмерика  летнее времÑ
Ora de vară a Europei de Est

如您所见,我应该能够检索数据,但还没有找到方法。
有人能帮我解决这个问题吗?
请记住,我要做的就是将正确呈现形式的unicode数据写入文件,或者将正确呈现形式的unicode数据发送到我的新数据库。

ajsxfq5m

ajsxfq5m1#

对于表情符号,必须使用mysql的 CHARACTER SET utf8mb4 . 最好的方法是在连接过程中确定这一点。其次是via SET NAMES utf8mb4; .

SELECT UNHEX('646f6ee2809974207274f09f989b');

产量

don’t rt😛

这是给你的吗?但是,使用 SELECT HEX(col) ... 看看里面有什么 col ; 在代码显示十六进制之前,数据可能已损坏。

SELECT CONVERT(BINARY(CONVERT('d’Europe' USING latin1)) USING utf8mb4);
yields   d’Europe   mojibake to ut8 (or utf8mb4),

要进行更多调试,请执行以下操作

SELECT LENGTH(col), CHAR_LENGTH(col), col, HEX(col) FROM ...

如果它是双重编码的,我们可以从两个不同的长度检测它。
你给我看 CREATE TABLE ,但是加载数据的代码呢?那丢弃它的代码呢?或者你升级到位了吗?我的观点是mojibake(etc)可能在升级期间发生。

SELECT LENGTH('d’Europe'), CHAR_LENGTH('d’Europe'), 'd’Europe', HEX('d’Europe');
+----------------------+---------------------------+------------+----------------------+
| LENGTH('d’Europe')   | CHAR_LENGTH('d’Europe')   | d’Europe   | HEX('d’Europe')      |
+----------------------+---------------------------+------------+----------------------+
|                   10 |                         8 | d’Europe   | 64E280994575726F7065 |
+----------------------+---------------------------+------------+----------------------+

“双编码”十六进制是

64C3A2E282ACE284A24575726F7065

这显示了撤销它的最佳(?)方式:

SELECT CONVERT(BINARY(CONVERT(CONVERT(UNHEX('64C3A2E282ACE284A24575726F7065') USING utf8mb4) USING latin1)) USING utf8mb4);
+---------------------------------------------------------------------------------------------------------------------+
| CONVERT(BINARY(CONVERT(CONVERT(UNHEX('64C3A2E282ACE284A24575726F7065') USING utf8mb4) USING latin1)) USING utf8mb4) |
+---------------------------------------------------------------------------------------------------------------------+
| d’Europe                                                                                                            |
+---------------------------------------------------------------------------------------------------------------------+

我浏览了一下你提供的其他几行;他们似乎是一致的。也就是说,使其中一个起作用的修复将应用于其他修复(一定要用表情符号勾选一个。)
更多
这是一个硬汉。
这是第一排
SELECT HEX(col) '646F6EC3A2C280C2997420676F20627265616B696E67206D79206865617274C3B0C29FC298C29B' 对于 don’t go breaking my heart😛 让我从“正确的单引号”开始( )因为它似乎具有代表性。

Char   UTF-8              If interpreted as latin1
’      E28099             ’
’    C3A2 C280   C299      You have this
’    C3A2 E282AC E284A2    Correct encoding

有东西变了 进入 C280 . 虽然反向工作“正确”,但它不是有效的utf-8Map。我不知道发生在哪里;我怀疑它是否在mysql中。Map是在某个客户端中完成的吗?
正如我们从中看到的,mysql不想使用 C280 :

mysql> SELECT CAST(UNHEX('C3A2E282ACE284A2') AS char), CAST(UNHEX('C3A2C280C299') AS char);
+-----------------------------------------+-------------------------------------+
| CAST(UNHEX('C3A2E282ACE284A2') AS char) | CAST(UNHEX('C3A2C280C299') AS char) |
+-----------------------------------------+-------------------------------------+
| ’                                     | â                                 |
+-----------------------------------------+-------------------------------------+

所以,在mysql中,我看不到一个简单的方法来清理混乱。然而,这是相当直接的转向 C280 进入 80 .
如果你能找到绳子 don’t 然后将字符串反馈回mysql,再加上一个转换表达式,就可以修复它了。
直到十多年前, C280 这是黑客用来让淘气的东西通过浏览器的伎俩的一部分。但是浏览器变得聪明了。
如果你能找到 C280 来自,请随软件提交安全缺陷报告。停止使用它。
如果是20年前的mysql,您可能已经使用了 latin1 在版本4.0(没有其他字符集)或4.1中,不正确地从拉丁语1转换而来。或者可能仍然使用拉丁文1,但使用utf-8字符只是跨越2-3个拉丁文1字符。在这一点上,答案是错误的 ALTER 导致“双重编码”,但您可能没有注意到,因为在 INSERT 在这段时间里大部分都没有完成 SELECT .
mysql 5.5(2010年底)推出了utf8mb4,它可以正确处理表情符号。
(与此同时,表情符号看起来像是同样的黑客双重编码。)

7eumitmz

7eumitmz2#

考虑到现在没有人发布真正有用的解决方案,我想我应该逆势而行。
如果您曾经遇到过这个问题,并且希望使用java8提取双重编码数据并将其写入转储文件(如csv文件或sql文件),请尝试将其作为项目的启动程序。。。

// Create and fill a list of maps called recs
// Note: BaseMap is just my own class that extends Map, it has extensions like getString, getInt, etc
// I'm using column called "txt" to store some UTF8 test data

Path path = Paths.get("/my/file/name/here"); // <<< change this
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {

    for(int i=0; i<recs.size(); i++)
    {
        BaseMap rec = new BaseMap((Map)recs.get(i));
        try {
            byte[] doubleEncodedBytes = rec.getString("txt").getBytes("ISO-8859-1");
            String decodedTxt = new String(doubleEncodedBytes, "UTF-8");
            writer.append(decodedTxt);
            writer.newLine();
            // If you don't want to write to a file, 
            // you could instead execute on a connection to Database 2: INSERT INTO `schemaname`.`tablename` (columns...) VALUES (" decodedTxt ",....)
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    writer.flush();
} catch (IOException e) {
    e.printStackTrace();
}

是的,这是一个缓慢的解决方案,但我读过并尝试过的所有mysql解决方案都失败了,所以可以说这是我的“最终解决方案”

相关问题