我数据库中的 "UTF8" 数据真的编码正确吗?
Is the "UTF8" data in my database really encoded correctly?
我有一个 PHP 应用程序,它的 MYSQL 数据库 "should" 包含 UTF8 编码数据。关于 unicode 字符,我的应用程序似乎从头到尾都能正常工作。如果有人将 "Strömgren" 提交到我的数据库(通过 HTML 表单),当我取回数据时我会看到 "Strömgren",等等
我的数据库表都是 UTF8,我的 html 页面和表单都是 charset=utf-8。
我最近注意到在我的应用程序的一部分中,我的 unicode 字符似乎是双重编码的。当我显示应该是 Strömgren 的内容时,我看到了 Strömgren -- Str\xc3\xb6mgren 与 Str\xc3\x83\xc2\xb6mgren。如果我 utf8_decode 错误的字符串,它看起来又正确了。
我假设这是 "double-encoding."
我发现显示双重编码数据的应用程序部分使用不同的代码来建立其数据库连接,并且该代码正在进行以下调用:
$db->set_charset("utf8")
我本来打算为我的所有数据库连接都这样做,但不知何故最终只在一个地方这样做。所以,几乎我所有的应用程序都使用没有 set_charset 命令的连接,并且 Strömgren 看起来总是正确的,而唯一的一段代码确实有 set_charset("utf8") (而且只有曾经从数据库读取,从不写入),显示不正确。
我不确定这是怎么回事,但我怀疑我数据库中的数据并不是真正以 UTF8 编码存储的?也许当我发送 Strömgren(没有 set_charset("utf8"))时,它认为它正在接收 latin1(或其他),当我读回它时,我收到了 latin1,但是因为我的 html 页面有 "charset=utf-8" 它正在 "mis-displayed" 作为 Strömgren,而实际上数据库认为它正在向我发送 Strömgren。 (我可能说的不是正确或清楚,但我希望它能被理解。)
我有两个问题:
首先,我的想法是否有道理,或者我完全没有根据?
其次,我确定数据库中的数据是否编码错误(即数据库实际上是否包含 Strömgren 或 Strömgren)的最佳方法是什么?
每个 table 都有一个默认字符集和一个用于存储其值的排序规则。
使用以下方法找到它们:
SHOW FULL COLUMNS FROM table_name;
和
SHOW CREATE TABLE table_name;
然后你可以改变一个 table 来拥有像这样的 UTF-8:
ALTER TABLE tbl_name
CONVERT TO CHARACTER SET 'UTF-8'
修复定义为 latin1 并填充 UTF-8 数据的 table 的编码:
ALTER TABLE table_name CHANGE field field blob;
ALTER TABLE table_name CHANGE field field text charset utf8;
查看实际存储内容的一种方法是使用 HEX
函数。 (这是最接近 Oracle 风格的 DUMP() 函数的 MySQL。
这是一个演示,展示了如何使用 HEX 函数 return 存储的内容...
CREATE TABLE foo
( foo_lat VARCHAR(10) CHARSET latin1
, foo_utf VARCHAR(10) CHARSET utf8
);
INSERT INTO foo (foo_lat, foo_utf) VALUES
( UNHEX('6dc3b1c3b6'), UNHEX('6dc3b1c3b6') );
SELECT foo_lat
, foo_utf
, HEX(foo_lat)
, HEX(foo_utf)
FROM foo ;
foo_lat foo_utf HEX(foo_lat) HEX(foo_utf)
--------- ------- ------------ --------------
mñö mñö 6DC3B1C3B6 6DC3B1C3B6
你的思路似乎很清晰。
set_charset
函数是使用 msyqli 接口指定客户端字符集的推荐方法。
我有点好奇你之前的字符集是什么运行那个。
$db->character_set_name();
我也很好奇...从同一个连接,下面的查询 returns 是什么。
SELECT @@session.character_set_client
, @@session.character_set_connection
, @@session.character_set_results
, @@session.character_set_server
, @@global.character_set_client
, @@global.character_set_connection
, @@global.character_set_results
, @@global.character_set_system
...来自显示字符 "correctly" 的示例代码副本和显示字符 "incorrectly" 的示例代码副本,在 和 完成 set_charset
.
之后
如果您在任何地方看到 latin1
,那可能是个问题。
如果在 latin1
列中存储了 UTF-8
个编码值,那就有问题了。当您使用 utf8
字符集从数据库中提取这些值时,这些值将得到 "double encoded".
因此,验证您在列上的字符集是 utf8
。
警告: 如果确实有 UTF-8
值存储在 latin
列中,请勿尝试通过将列转换为 utf8
,对存储值进行双重编码会使问题变得更糟。
如果您想尝试一下,请在 单独的 测试数据库上进行;现在可能是测试是否将您的 mysqldump 备份恢复到另一个 test MySQL 实例的好时机 test 机器正在工作。如果由 mysqldump 生成的 .sql 文件出现错误,您希望现在发现它,而不是等到您实际需要进行恢复时才发现。)
注意:重要的是列定义中的字符集。 table 上的设置只是一个 默认 值,当它未在列上指定时使用。数据库级别的设置只是一个 default,当 table 被创建时没有指定字符集时使用。
也就是说,更改数据库的字符集不会影响现有的 table 和列。它将对任何未指定字符集的 CREATE TABLE
产生影响。
SHOW CREATE TABLE foo
是查看 table 和列的实际字符集的便捷方式。
Strömgren
而不是 Strömgren
表示 Mojibake。
如果 SELECT HEX(...) FROM ...
给你 53 74 72 C3B6 6D 67 72 65 6E
(没有空格),你已经正确存储了 utf8 编码。 C3B6
是 ö
.
的 utf8 十六进制
"Double encoding" 将显示 53 74 72 C383 C2B6 6D 67 72 65 6E
,其中 C383
和 C2B6
是 Ã
和 ¶
的 utf8 十六进制。
参见 duplicate 讨论和解决方案,包括如何通过一对 ALTER TABLEs
.
恢复数据
也就是说,何塞和斯宾塞都有完整答案的要素。
我有一个 PHP 应用程序,它的 MYSQL 数据库 "should" 包含 UTF8 编码数据。关于 unicode 字符,我的应用程序似乎从头到尾都能正常工作。如果有人将 "Strömgren" 提交到我的数据库(通过 HTML 表单),当我取回数据时我会看到 "Strömgren",等等
我的数据库表都是 UTF8,我的 html 页面和表单都是 charset=utf-8。
我最近注意到在我的应用程序的一部分中,我的 unicode 字符似乎是双重编码的。当我显示应该是 Strömgren 的内容时,我看到了 Strömgren -- Str\xc3\xb6mgren 与 Str\xc3\x83\xc2\xb6mgren。如果我 utf8_decode 错误的字符串,它看起来又正确了。
我假设这是 "double-encoding."
我发现显示双重编码数据的应用程序部分使用不同的代码来建立其数据库连接,并且该代码正在进行以下调用:
$db->set_charset("utf8")
我本来打算为我的所有数据库连接都这样做,但不知何故最终只在一个地方这样做。所以,几乎我所有的应用程序都使用没有 set_charset 命令的连接,并且 Strömgren 看起来总是正确的,而唯一的一段代码确实有 set_charset("utf8") (而且只有曾经从数据库读取,从不写入),显示不正确。
我不确定这是怎么回事,但我怀疑我数据库中的数据并不是真正以 UTF8 编码存储的?也许当我发送 Strömgren(没有 set_charset("utf8"))时,它认为它正在接收 latin1(或其他),当我读回它时,我收到了 latin1,但是因为我的 html 页面有 "charset=utf-8" 它正在 "mis-displayed" 作为 Strömgren,而实际上数据库认为它正在向我发送 Strömgren。 (我可能说的不是正确或清楚,但我希望它能被理解。)
我有两个问题:
首先,我的想法是否有道理,或者我完全没有根据?
其次,我确定数据库中的数据是否编码错误(即数据库实际上是否包含 Strömgren 或 Strömgren)的最佳方法是什么?
每个 table 都有一个默认字符集和一个用于存储其值的排序规则。 使用以下方法找到它们:
SHOW FULL COLUMNS FROM table_name;
和
SHOW CREATE TABLE table_name;
然后你可以改变一个 table 来拥有像这样的 UTF-8:
ALTER TABLE tbl_name
CONVERT TO CHARACTER SET 'UTF-8'
修复定义为 latin1 并填充 UTF-8 数据的 table 的编码:
ALTER TABLE table_name CHANGE field field blob;
ALTER TABLE table_name CHANGE field field text charset utf8;
查看实际存储内容的一种方法是使用 HEX
函数。 (这是最接近 Oracle 风格的 DUMP() 函数的 MySQL。
这是一个演示,展示了如何使用 HEX 函数 return 存储的内容...
CREATE TABLE foo
( foo_lat VARCHAR(10) CHARSET latin1
, foo_utf VARCHAR(10) CHARSET utf8
);
INSERT INTO foo (foo_lat, foo_utf) VALUES
( UNHEX('6dc3b1c3b6'), UNHEX('6dc3b1c3b6') );
SELECT foo_lat
, foo_utf
, HEX(foo_lat)
, HEX(foo_utf)
FROM foo ;
foo_lat foo_utf HEX(foo_lat) HEX(foo_utf)
--------- ------- ------------ --------------
mñö mñö 6DC3B1C3B6 6DC3B1C3B6
你的思路似乎很清晰。
set_charset
函数是使用 msyqli 接口指定客户端字符集的推荐方法。
我有点好奇你之前的字符集是什么运行那个。
$db->character_set_name();
我也很好奇...从同一个连接,下面的查询 returns 是什么。
SELECT @@session.character_set_client
, @@session.character_set_connection
, @@session.character_set_results
, @@session.character_set_server
, @@global.character_set_client
, @@global.character_set_connection
, @@global.character_set_results
, @@global.character_set_system
...来自显示字符 "correctly" 的示例代码副本和显示字符 "incorrectly" 的示例代码副本,在 和 完成 set_charset
.
如果您在任何地方看到 latin1
,那可能是个问题。
如果在 latin1
列中存储了 UTF-8
个编码值,那就有问题了。当您使用 utf8
字符集从数据库中提取这些值时,这些值将得到 "double encoded".
因此,验证您在列上的字符集是 utf8
。
警告: 如果确实有 UTF-8
值存储在 latin
列中,请勿尝试通过将列转换为 utf8
,对存储值进行双重编码会使问题变得更糟。
如果您想尝试一下,请在 单独的 测试数据库上进行;现在可能是测试是否将您的 mysqldump 备份恢复到另一个 test MySQL 实例的好时机 test 机器正在工作。如果由 mysqldump 生成的 .sql 文件出现错误,您希望现在发现它,而不是等到您实际需要进行恢复时才发现。)
注意:重要的是列定义中的字符集。 table 上的设置只是一个 默认 值,当它未在列上指定时使用。数据库级别的设置只是一个 default,当 table 被创建时没有指定字符集时使用。
也就是说,更改数据库的字符集不会影响现有的 table 和列。它将对任何未指定字符集的 CREATE TABLE
产生影响。
SHOW CREATE TABLE foo
是查看 table 和列的实际字符集的便捷方式。
Strömgren
而不是 Strömgren
表示 Mojibake。
如果 SELECT HEX(...) FROM ...
给你 53 74 72 C3B6 6D 67 72 65 6E
(没有空格),你已经正确存储了 utf8 编码。 C3B6
是 ö
.
"Double encoding" 将显示 53 74 72 C383 C2B6 6D 67 72 65 6E
,其中 C383
和 C2B6
是 Ã
和 ¶
的 utf8 十六进制。
参见 duplicate 讨论和解决方案,包括如何通过一对 ALTER TABLEs
.
也就是说,何塞和斯宾塞都有完整答案的要素。