我可以安全地使用带有 utf8 列的 utf8mb4 连接吗?
Posted
技术标签:
【中文标题】我可以安全地使用带有 utf8 列的 utf8mb4 连接吗?【英文标题】:Can I safely use a utf8mb4 connection with utf8 columns? 【发布时间】:2016-04-08 07:41:09 【问题描述】:我有一些带有 utf8mb4 字段的 mysql 表,还有一些带有 utf8 字段的 MySQL 表。
在所有表的 PDO 连接字符串中使用 utf8mb4 是否安全?还是我必须将所有内容都转换为 utf8mb4,或者启动两个不同的 PDO 连接?
编辑:问题不是 “我可以将 4 字节字符存储到 utf8 列中吗?” 我们已经知道我们不能,这不取决于连接,所以如果一个列是 utf8,这意味着它不会接收 4 字节字符,例如国家或货币代码、电子邮件地址、用户名……输入由应用程序验证。
【问题讨论】:
问题含糊不清。您是否会存储 4 字节 utf8mb4 字符,例如 ???????????? ? 【参考方案1】:这可以很容易地使用以下脚本进行测试:
<?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
作为连接字符集是安全的。
【讨论】:
谢谢保罗,我通过这样做发现了同样的情况。但是由于我必须为企业项目评估这个,我不能依赖测试:正如我在赏金中所说,我正在“寻找来自可靠和/或官方来源的答案。”,其中“官方”是关键字. @Dr.GianluigiZaneZanetini - 什么比可重复的测试更可信?您甚至可以使用所有utf8
(3 字节)字符运行测试。
更可信的是官方文档说明了这一点。 “让我们试试看会发生什么”的问题是你总是会错过一些测试用例。
@Dr.GianluigiZaneZanetini - 我怀疑你会找到类似“使用字符集 X 进行连接和字符集 Y 存储数据是安全的,如果你只使用来自是”。他们需要为每对字符集编写它。你甚至不会找到一个声明,它告诉你可以安全地使用 utf8mb4 连接来获取 utf8mb4 数据。 (不-这不是错字-我的意思是相同的字符集)您需要解释文档。我的解释是我回答的最后一句话。阅读Chapter 10 - 完全。【参考方案2】:
简短回答: 是的,如果您只使用 3 字节(或更短)的 UTF-8 字符。
或者... 否,如果您打算使用 4 字节 UTF-8 字符,例如 ???。
长答案:
(我将解释为什么“不”可能是正确答案。)
连接确定客户端正在使用什么编码。
列上的CHARACTER SET
(或默认情况下,来自表)确定可以将什么编码放入列中。
CHARACTER SET utf8
是utf8mb4
的子集。也就是说,utf8
(通过连接或列)可以接受的所有字符都可以为utf8mb4
接受。换句话说,MySQL 的utf8mb4
(与外界的UTF-8
相同)具有完整的 4 字节 utf-8 编码,比 MySQL 最多 3 字节的 utf8
包含更多的 Emoji、更多的中文等(又名“BMP”)
(从技术上讲,utf8mb4
最多只能处理 4 个字节,但UTF-8
可以处理更长的字符。但是,我怀疑在我的一生中是否会出现 5 个字节的字符。)
因此,假设连接为 utf8mb4 且表中的列仅为 utf8,则客户端中任何 3 字节(或更短)的 UTF-8 字符会发生以下情况:每个字符都进出服务器没有转换,没有错误。注意:问题出现在INSERT
,而不是SELECT
;但是,在您执行SELECT
之前,您可能不会注意到问题。
但是,如果您在客户端中有表情符号怎么办?现在你会得到一个错误。 (或截断的字符串)(或问号)这是因为 4 字节的 Emoji(例如?)无法压缩到 3 字节的“utf8”(或“1 字节 latin1”或... )。
如果您运行的是 5.5 或 5.6,您可能会遇到 767(或 191)问题。我在here 中提供了几种解决方法。没有一个是完美的。
至于反转(utf8 连接但 utf8mb4 列):如果您设法将一些 4 字节字符放入表中,SELECT
可能会遇到麻烦。
“官方来源”——祝你好运。我花了十年时间试图梳理字符处理的来龙去脉,然后将其简化为可操作的句子。大部分时间都在想我有所有的答案,结果却遇到了另一个失败的测试用例。常见情况在Trouble with UTF-8 characters; what I see is not what I stored 中列出。但是,这并不能直接解决您的问题!
来自评论
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)
上面说“连接”(认为“客户端”)使用的是 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)
现在,将“连接”更改为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 并且表中的列只有 utf8” @Dr.GianluigiZaneZanetini - 连接参数说明 client 的编码是什么。如果错误,可能会发生各种错误(请参阅我链接到的其他错误)。如果连接指定您在客户端中只有 3 字节的“utf8”,那么在客户端中使用 4 字节的 Emoji 是一种“错误”形式。 @Dr.GianluigiZaneZanetini - 也许我刚刚添加到我的答案中的内容直接解决了您的评论。【参考方案3】:简短回答:否,这不安全。
如果您的数据包含utf8mb4
字符并且您使用的是 MySQL utf8
字符集连接,那么您将遇到问题,因为 MySQL utf8
字符集仅支持 BMP 字符(最多 3 个字节字符)。
我的建议是将所有表转换为 utf8mb4
以获得完整的 UTF-8 支持。此外,utf8mb4
与utf8
向后兼容。
【讨论】:
向后兼容但存在一些问题:InnoDB 的最大可索引长度从 255 下降到 191 个字符,固定长度字段(例如 CHAR)每个字符多使用一个字节 @the_nuts 说的好。还有更多信息here 你回答了倒置的问题。不,将 utf8 连接与 utf8mb4 列一起使用是不安全的,但它不应该反过来正常工作,这是 OP 所要求的吗? @Accountantم 你说得对,我接受它是因为它是唯一的,但它没有回答问题 实际上我接受了它,但最后我使用的是处理一些 utf8 和拉丁列的 utf8mb4 连接,没有任何问题以上是关于我可以安全地使用带有 utf8 列的 utf8mb4 连接吗?的主要内容,如果未能解决你的问题,请参考以下文章
排序规则将 utf8mb4_unicode_ci 更改为 utf8mb4_general_ci
utf8mb4_unicode_ci 与 utf8mb4_bin
utf8mb4_unicode_ci 在 PhpMyAdmin 中选择,但 WordPress 表使用 utf8mb4_unicode_520_ci 排序规则