Можно ли безопасно использовать соединение utf8mb4 с столбцами utf8?
У меня есть некоторые таблицы MySQL с полями utf8mb4, а другие с utf8.
Безопасно ли использовать utf8mb4 в строке подключения PDO для всех таблиц? Или мне нужно преобразовать все в utf8mb4 или запустить два разных подключения PDO?
РЕДАКТИРОВАТЬ: Вопрос не в том, "могу ли я хранить 4-байтовые символы в столбцах utf8?" Мы уже знаем, что не можем, это не зависит от соединения, поэтому, если столбец имеет значение utf8, это означает, что он не получит 4-байтовые символы, например, коды страны или валюты, адреса электронной почты, имена пользователей... где ввод подтверждено приложением.
Ответы
Ответ 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 äŒé ♔♕ 🛃 🔣',
),
)
Как видите, мы не можем использовать utf8
качестве кодировки соединения, когда работаем со столбцами utf8mb4
(см. ????
). Но мы можем использовать utf8mb4
для соединения при работе со столбцами utf8
. Также нет проблем с записью и чтением из latin
или ascii
столбцов.
Причина в том, что вы можете кодировать любой utf8
, latin
или ascii
символ в utf8mb4
но не наоборот. Поэтому использование utf8mb4
качестве набора символов для соединения в этом случае безопасно.
Ответ 2
Короткий ответ: НЕТ, это не безопасно.
Если у ваших данных есть символы utf8mb4
, и вы используете соединение charset MySQL utf8
, вы столкнулись с проблемами, поскольку MySQL utf8
charset поддерживает только символы BMP (до 3 байтов символов).
Моя рекомендация состоит в том, чтобы преобразовать все таблицы в utf8mb4
для полной поддержки UTF-8. Кроме того, utf8mb4
имеет обратную совместимость с utf8
.
Ответ 3
Краткий ответ: Да, если вы используете только 3-байтовые (или более короткие) символы UTF-8.
Или... Нет, если вы собираетесь работать с 4-байтовыми символами UTF-8, такими как 😅😘😍.
Длинный ответ:
(И я расскажу, почему "нет" может быть правильным ответом.)
Соединение устанавливает, какую кодировку использует клиент.
CHARACTER SET
для столбца (или, по умолчанию, из таблицы) устанавливает, какую кодировку можно поместить в столбец.
CHARACTER SET utf8
является подмножеством utf8mb4
. То есть все символы, приемлемые для utf8
(через соединение или столбец), приемлемы для utf8mb4
. Иными словами, MySQL utf8mb4
(такой же, как внешний мир UTF-8
) имеет полную 4-байтовую кодировку utf-8, которая включает в себя больше Emoji, больше китайского и т.д., Чем MySQL до 3-байтового utf8
(он же BMP) ")
(Технически utf8mb4
обрабатывает только до 4 байтов, но UTF-8
обрабатывает более длинные символы. Однако я сомневаюсь, что 5-байтовые символы появятся в моей жизни.)
Итак, вот что происходит с любым 3-байтовым (или более коротким) символом UTF-8 в клиенте, учитывая, что Connection имеет значение utf8mb4, а столбцы в таблицах - только utf8: каждый символ входит и выходит из сервера без преобразования и без ошибок. Примечание: проблема возникает на INSERT
, а не на SELECT
; однако вы можете не заметить проблему, пока не сделаете SELECT
.
Но что, если у вас есть Emoji в клиенте? Теперь вы получите ошибку. (Или усеченная строка) (Или вопросительный знак (и)) Это связано с тем, что 4-байтовый Emoji (например, cannot) не может быть сжат в 3-байтовый "utf8" (или "1-байтовый latin1" или...).
Если вы используете 5.5 или 5.6, вы можете столкнуться с проблемой 767 (или 191). Я приведу несколько обходных путей здесь. Ни один не идеален.
Что касается инвертирования (соединение utf8, но столбцы utf8mb4): у SELECT
могут возникнуть проблемы, если вам удастся получить некоторые 4-байтовые символы в таблице.
"Официальные источники" - удачи. Я потратил десятилетие, пытаясь разобраться в тонкостях обработки символов, а затем упростил их до практических предложений. Большую часть времени я думал, что у меня есть ответы на все вопросы, только чтобы встретить еще один неудачный тестовый пример. Распространенные случаи перечислены в Trouble с символами UTF-8; то, что я вижу, не то, что я храню Однако это не относится непосредственно к вашему вопросу!
Из комментария
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)