我可以安全地将 utf8mb4 连接与 utf8 列一起使用吗?
Can I safely use a utf8mb4 connection with utf8 columns?
我有一些带有 utf8mb4 字段的 MySQL 表,还有一些带有 utf8.
的表
在所有表的 PDO 连接字符串中使用 utf8mb4 是否安全?或者我是否必须将所有内容都转换为 utf8mb4,或者启动两个不同的 PDO 连接?
编辑:问题不是 "can I store 4-byte characters into utf8 columns?" 我们已经知道我们不能,这不取决于连接,所以如果列是 utf8 它表示它将不会接收 4 字节字符,例如国家或货币代码、电子邮件地址、用户名...其中输入由应用程序验证。
简答:否,不安全。
如果您的数据有 utf8mb4
个字符,并且您使用的是 MySQL utf8
字符集连接,您将 运行 遇到问题,因为 MySQL utf8
字符集仅支持 BMP 字符(最多 3 个字节的字符)。
我的建议是将所有表格转换为 utf8mb4
以获得完整的 UTF-8 支持。此外,utf8mb4
向后兼容 与 utf8
。
这可以使用以下脚本很容易地进行测试:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'test', '');
$pdo->exec("
drop table if exists utf8_test;
create table utf8_test(
conn varchar(50) collate ascii_bin,
column_latin1 varchar(50) collate latin1_general_ci,
column_utf8 varchar(50) collate utf8_unicode_ci,
column_utf8mb4 varchar(50) collate utf8mb4_unicode_ci
);
");
$latin = 'abc äŒé';
$utf8 = '♔♕';
$mb4 = ' ';
$pdo->exec("set names utf8");
$pdo->exec("
insert into utf8_test(conn, column_latin1, column_utf8, column_utf8mb4)
values ('utf8', '$latin', '$latin $utf8', '$latin $utf8 $mb4')
");
$pdo->exec("set names utf8mb4");
$pdo->exec("
insert into utf8_test(conn, column_latin1, column_utf8, column_utf8mb4)
values ('utf8mb4', '$latin', '$latin $utf8', '$latin $utf8 $mb4')
");
$result = $pdo->query('select * from utf8_test')->fetchAll(PDO::FETCH_ASSOC);
var_export($result);
这是结果:
array (
0 =>
array (
'conn' => 'utf8',
'column_latin1' => 'abc äŒé',
'column_utf8' => 'abc äŒé ♔♕',
'column_utf8mb4' => 'abc äŒé ♔♕ ???? ????',
),
1 =>
array (
'conn' => 'utf8mb4',
'column_latin1' => 'abc äŒé',
'column_utf8' => 'abc äŒé ♔♕',
'column_utf8mb4' => 'abc äŒé ♔♕ ',
),
)
如您所见,当我们使用 utf8mb4
列时,我们不能使用 utf8
作为连接字符集(请参阅 ????
)。但是我们可以在使用 utf8
列时使用 utf8mb4
进行连接。写入和读取 latin
或 ascii
列也没有问题。
原因是您可以在 utf8mb4
中编码任何 utf8
、latin
或 ascii
字符,但反之则不行。所以在这种情况下使用 utf8mb4
作为连接的字符集是安全的。
简短回答: 是,如果您只使用 3 字节(或更短)的 UTF-8 字符。
或者... 否 如果您打算使用 4 字节 UTF-8 字符,例如 .
长答案:
(我会解释为什么 "no" 可能 是正确的答案。)
连接确定客户端使用的编码。
列上的 CHARACTER SET
(或者,默认情况下,来自 table)确定可以将什么编码放入该列。
CHARACTER SET utf8
是 utf8mb4
的子集。也就是说,acceptable到utf8
(通过连接或列)的所有字符都是acceptable到utf8mb4
。换句话说,MySQL 的 utf8mb4
(与外界的 UTF-8
相同)具有完整的 4 字节 utf-8 编码,比 MySQL 最多 3 个字节 utf8
(又名 "BMP")
(从技术上讲,utf8mb4
最多只能处理 4 个字节,但 UTF-8
可以处理更长的字符。但是,我怀疑我有生之年是否会出现 5 个字节的字符。)
因此,如果连接是 utf8mb4 并且 tables 中的列仅为 utf8,那么客户端中任何 3 字节(或更短的)UTF-8 字符都会发生这种情况:每个字符进出服务器没有转换,没有错误。注意:问题出现在INSERT
,而不是SELECT
;但是,在执行 SELECT
.
之前,您可能不会注意到问题
但是,如果客户端中有表情符号怎么办?现在你会得到一个错误。 (或 t运行 字符串)(或问号)这是因为 4 字节的表情符号(例如,)不能被压缩到 3 字节的 "utf8" (或“1 字节latin1" 或 ...).
如果您运行宁 5.5 或 5.6,您可能 运行 进入 767(或 191)问题。我在 here 中提供了几种解决方法。 None 完美。
至于反转(utf8 连接但 utf8mb4 列):如果您设法将一些 4 字节字符放入 table,SELECT
可能会遇到麻烦。
"Official sources" -- 祝你好运。我花了十年时间试图梳理角色处理的来龙去脉,然后将其简化为可操作的句子。那段时间大部分时间都在想我已经有了所有的答案,结果却遇到了另一个失败的测试用例。 中列出了常见情况。然而,这并没有直接解决你的问题!
来自评论
mysql> SHOW CREATE TABLE emoji\G
*************************** 1. row ***************************
Table: emoji
Create Table: CREATE TABLE `emoji` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`text` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
mysql> insert into emoji (text) values ("abc");
Query OK, 1 row affected (0.01 sec)
mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
上面说 "connection"(认为 "client")使用的是 utf8,而不是 utf8mb4。
mysql> insert into emoji (text) values (""); -- 4-byte Emoji
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+----------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------------------------------------------------+
| Warning | 1366 | Incorrect string value: '\xF0\x9F\x98\x85\xF0\x9F...' for column 'text' at row 1 |
+---------+------+----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
现在,将 'connection' 更改为 utf8mb4
:
mysql> SET NAMES utf8mb4;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into emoji (text) values ("");
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM emoji;
+----+--------------+
| id | text |
+----+--------------+
| 1 | ? ? ? ? |
| 2 | abc |
| 3 | ???????????? | -- from when "utf8" was in use
| 4 | | -- Success with utf8mb4 in use
+----+--------------+
4 rows in set (0.01 sec)
我有一些带有 utf8mb4 字段的 MySQL 表,还有一些带有 utf8.
的表在所有表的 PDO 连接字符串中使用 utf8mb4 是否安全?或者我是否必须将所有内容都转换为 utf8mb4,或者启动两个不同的 PDO 连接?
编辑:问题不是 "can I store 4-byte characters into utf8 columns?" 我们已经知道我们不能,这不取决于连接,所以如果列是 utf8 它表示它将不会接收 4 字节字符,例如国家或货币代码、电子邮件地址、用户名...其中输入由应用程序验证。
简答:否,不安全。
如果您的数据有 utf8mb4
个字符,并且您使用的是 MySQL utf8
字符集连接,您将 运行 遇到问题,因为 MySQL utf8
字符集仅支持 BMP 字符(最多 3 个字节的字符)。
我的建议是将所有表格转换为 utf8mb4
以获得完整的 UTF-8 支持。此外,utf8mb4
向后兼容 与 utf8
。
这可以使用以下脚本很容易地进行测试:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'test', '');
$pdo->exec("
drop table if exists utf8_test;
create table utf8_test(
conn varchar(50) collate ascii_bin,
column_latin1 varchar(50) collate latin1_general_ci,
column_utf8 varchar(50) collate utf8_unicode_ci,
column_utf8mb4 varchar(50) collate utf8mb4_unicode_ci
);
");
$latin = 'abc äŒé';
$utf8 = '♔♕';
$mb4 = ' ';
$pdo->exec("set names utf8");
$pdo->exec("
insert into utf8_test(conn, column_latin1, column_utf8, column_utf8mb4)
values ('utf8', '$latin', '$latin $utf8', '$latin $utf8 $mb4')
");
$pdo->exec("set names utf8mb4");
$pdo->exec("
insert into utf8_test(conn, column_latin1, column_utf8, column_utf8mb4)
values ('utf8mb4', '$latin', '$latin $utf8', '$latin $utf8 $mb4')
");
$result = $pdo->query('select * from utf8_test')->fetchAll(PDO::FETCH_ASSOC);
var_export($result);
这是结果:
array (
0 =>
array (
'conn' => 'utf8',
'column_latin1' => 'abc äŒé',
'column_utf8' => 'abc äŒé ♔♕',
'column_utf8mb4' => 'abc äŒé ♔♕ ???? ????',
),
1 =>
array (
'conn' => 'utf8mb4',
'column_latin1' => 'abc äŒé',
'column_utf8' => 'abc äŒé ♔♕',
'column_utf8mb4' => 'abc äŒé ♔♕ ',
),
)
如您所见,当我们使用 utf8mb4
列时,我们不能使用 utf8
作为连接字符集(请参阅 ????
)。但是我们可以在使用 utf8
列时使用 utf8mb4
进行连接。写入和读取 latin
或 ascii
列也没有问题。
原因是您可以在 utf8mb4
中编码任何 utf8
、latin
或 ascii
字符,但反之则不行。所以在这种情况下使用 utf8mb4
作为连接的字符集是安全的。
简短回答: 是,如果您只使用 3 字节(或更短)的 UTF-8 字符。
或者... 否 如果您打算使用 4 字节 UTF-8 字符,例如 .
长答案:
(我会解释为什么 "no" 可能 是正确的答案。)
连接确定客户端使用的编码。
列上的 CHARACTER SET
(或者,默认情况下,来自 table)确定可以将什么编码放入该列。
CHARACTER SET utf8
是 utf8mb4
的子集。也就是说,acceptable到utf8
(通过连接或列)的所有字符都是acceptable到utf8mb4
。换句话说,MySQL 的 utf8mb4
(与外界的 UTF-8
相同)具有完整的 4 字节 utf-8 编码,比 MySQL 最多 3 个字节 utf8
(又名 "BMP")
(从技术上讲,utf8mb4
最多只能处理 4 个字节,但 UTF-8
可以处理更长的字符。但是,我怀疑我有生之年是否会出现 5 个字节的字符。)
因此,如果连接是 utf8mb4 并且 tables 中的列仅为 utf8,那么客户端中任何 3 字节(或更短的)UTF-8 字符都会发生这种情况:每个字符进出服务器没有转换,没有错误。注意:问题出现在INSERT
,而不是SELECT
;但是,在执行 SELECT
.
但是,如果客户端中有表情符号怎么办?现在你会得到一个错误。 (或 t运行 字符串)(或问号)这是因为 4 字节的表情符号(例如,)不能被压缩到 3 字节的 "utf8" (或“1 字节latin1" 或 ...).
如果您运行宁 5.5 或 5.6,您可能 运行 进入 767(或 191)问题。我在 here 中提供了几种解决方法。 None 完美。
至于反转(utf8 连接但 utf8mb4 列):如果您设法将一些 4 字节字符放入 table,SELECT
可能会遇到麻烦。
"Official sources" -- 祝你好运。我花了十年时间试图梳理角色处理的来龙去脉,然后将其简化为可操作的句子。那段时间大部分时间都在想我已经有了所有的答案,结果却遇到了另一个失败的测试用例。
来自评论
mysql> SHOW CREATE TABLE emoji\G
*************************** 1. row ***************************
Table: emoji
Create Table: CREATE TABLE `emoji` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`text` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
mysql> insert into emoji (text) values ("abc");
Query OK, 1 row affected (0.01 sec)
mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
上面说 "connection"(认为 "client")使用的是 utf8,而不是 utf8mb4。
mysql> insert into emoji (text) values (""); -- 4-byte Emoji
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+----------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------------------------------------------------+
| Warning | 1366 | Incorrect string value: '\xF0\x9F\x98\x85\xF0\x9F...' for column 'text' at row 1 |
+---------+------+----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
现在,将 'connection' 更改为 utf8mb4
:
mysql> SET NAMES utf8mb4;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into emoji (text) values ("");
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM emoji;
+----+--------------+
| id | text |
+----+--------------+
| 1 | ? ? ? ? |
| 2 | abc |
| 3 | ???????????? | -- from when "utf8" was in use
| 4 | | -- Success with utf8mb4 in use
+----+--------------+
4 rows in set (0.01 sec)