218
Help us understand the problem. What are the problem?

posted at

updated at

UTF-8のテーブル(MySQL5.6)に竈門禰󠄀豆子が格納できない問題を調べてみた

竈門禰󠄀豆子をMySQL5.6のテーブルにinsertしようとすると正しく格納できず、竈門禰となってしまうケースがあるという話を聞き、調べてみました。

実践

まずは試しにやってみます。

mysql> show create table verification\G
*************************** 1. row ***************************
       Table: verification
Create Table: CREATE TABLE `verification` (
  `name` varchar(100) COLLATE utf8_bin DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
1 row in set (0.01 sec)

mysql> insert into verification (name) values ('竈門禰󠄀豆子');
Query OK, 1 row affected, 2 warnings (0.01 sec)


mysql> select * from verification;
+-----------+
| name      |
+-----------+
| 竈門禰    |
+-----------+
1 row in set (0.00 sec)

確かに竈門禰になっていることがわかりました。

調査

文字コード周りの設定を見てみます。

mysql> SHOW VARIABLES LIKE  'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | utf8                       |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | utf8                       |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.01 sec)

テーブルの文字コード設定はutf8です。公式リファレンスにも記載がありますが、MySQLのutf8文字セットは、3byteまでの文字を扱うことができます。

mysql> select * from CHARACTER_SETS where CHARACTER_SET_NAME like 'utf8%';
+--------------------+----------------------+---------------+--------+
| CHARACTER_SET_NAME | DEFAULT_COLLATE_NAME | DESCRIPTION   | MAXLEN |
+--------------------+----------------------+---------------+--------+
| utf8               | utf8_general_ci      | UTF-8 Unicode |      3 |
| utf8mb4            | utf8mb4_general_ci   | UTF-8 Unicode |      4 |
+--------------------+----------------------+---------------+--------+
2 rows in set (0.00 sec)

というわけで、文字が途中で途切れて格納されるのは文字コードの設定がutf8であることが一因のようです。

ただ、素直に考えるなら竈門禰で途切れているのでがutf8で扱えない4byte文字ということになります。こんなメジャーな漢字が4byte文字とは到底思えません。確認してみると、

>>> len('豆'.encode('utf-8'))
3

やっぱり3byteでした。そうなると疑わしいのは禰󠄀です。

よくよく見てみると、insertしようとした竈門禰󠄀豆子に対し、実際に登録されたのは竈門禰となっていて、偏がからに変わっていました。何故こんなことが起きるのでしょうか?

禰󠄀について掘り下げて調べてみます。

>>> len('禰󠄀'.encode('utf-8'))
7

7byte…?テーブル格納後のを見てみると…

>>> len('禰'.encode('utf-8'))
3

3byteになっています。insertのタイミングで4byteの何らかの情報が欠落してテーブルに格納されたようです。
禰󠄀は一体何者なのか、Unicodeのコードポイントを調べてみます。

>>> ord('禰󠄀')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 2 found       #2文字??
>>> s = '禰󠄀'
>>> list(s)
['禰', '󠄀']
>>> for i in list(s):
...     hex(ord(i))
... 
'0x79b0'
'0xe0100'

16進表現で0x79b00xe0100の2文字で構成されていることがわかりました。
0xe0100を調べてみると、これは異体字セレクタというもののようです。Wikipediaによれば、

異体字セレクタ (英: Variation Selector) は、Unicode および ISO/IEC 10646 (UCS) における、文字の字体をより詳細に指定するためのセレクタ (選択子) である。

とあり、を基底文字として偏が異なる文字であることを表現するためのセレクタが含まれていることがわかりました。

まとめ

なぜ竈門禰󠄀豆子がinsert時に一部欠落するかというと、

  • utf8のテーブルには4byte文字を格納できない
  • 禰󠄀の異体字であり、 + 4byteの異体字セレクタの組み合わせで表現される
  • したがって、禰󠄀を格納しようとすると、4byteである異体字セレクタのみ欠落して格納されてしまう

ということでした。異体字セレクタは今まで全く存在を知らず、文字コードの世界は奥が深い、、、と思わされる結果になりました。

追記

この問題を回避したい場合、4byte文字を扱えるutf8mb4を使いましょう。
(参考: https://dev.mysql.com/doc/refman/5.6/ja/charset-unicode-utf8mb4.html

なお、MySQL8系ではutf8mb4がデフォルトのようです。
(参考:https://dev.mysql.com/doc/refman/8.0/ja/server-system-variables.html#sysvar_character_set_client

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
218
Help us understand the problem. What are the problem?