MySQLで🍣が保存できない？utf8とutf8mb4の違いを実験

## はじめに：MySQLを触ってみたら絵文字でエラーが…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/810cd32b-d9b9-4935-ae1d-d615dcda3376.png)

MySQLを初めて触ってみたとき、「とりあえず文字コードはutf8にしておけば大丈夫だろう」と思っていました。ところが、絵文字を含むデータを保存しようとすると謎のエラーが発生...。調べてみると、MySQLの「utf8」には思わぬ落とし穴があることが分かりました。

同じようにMySQLを始めたばかりの方向けに、実際にGoogle Colabで再現しながら、この問題を理解していきましょう。

MySQLでWebアプリケーションを開発していて、こんな経験はありませんか？

- 🍣や😀などの絵文字を保存しようとしたらエラーが発生
- 「utf8」という文字コードを指定しているのに、なぜか文字化けが起こる
- 旧漢字や特殊な文字で予期しない問題が発生

実は、MySQLの「utf8」は**完全なUTF-8ではありません**。この記事では、Google Colabを使って実際にエラーを再現しながら、`utf8`と`utf8mb4`の違いを理解していきましょう。

## `utf8` と `utf8mb4` の違い

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/5eab9b69-bb43-40fd-a7f5-f14eb5671d74.png)


まず、基本的な違いを整理しておきます。

| 項目 | utf8 | utf8mb4 |
|------|------|---------|
| 正式名称 | UTF-8の3バイト版 | UTF-8の4バイト版（真のUTF-8） |
| 対応文字数 | 基本多言語面（BMP）のみ | Unicode全体 |
| バイト数 | 最大3バイト | 最大4バイト |
| 絵文字対応 | ❌ 不可 | ✅ 可能 |
| 旧漢字対応 | ❌ 一部不可 | ✅ 可能 |
| 推奨度 | 非推奨 | **推奨** |

**重要なポイント**：MySQLの「utf8」は歴史的な理由により、UTF-8の一部（3バイトまで）しか対応していません。現在では`utf8mb4`が標準として推奨されています。

## 実行環境について

この記事の内容は、以下の環境で動作確認を行いました：

- 実行環境：Google Colab（無料版）
- MySQLバージョン：8（aptでインストール）
- Pythonバージョン：3.10+
- 使用ライブラリ：
  - `mysql-connector-python`
  - `pandas`

## Google Colabで実験しよう！

実際に手を動かして確認してみましょう。Google Colabで以下のコードを実行すると、問題を再現できます。

### 前提：ColabでMySQL起動

```bash
!apt-get -y install mysql-server > /dev/null
!service mysql start
!mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/0d5e9cba-5733-4514-a55e-ee011da23df7.png)

### 【失敗編】utf8テーブルに🍣をINSERTしてみる

まずは`utf8`でデータベースとテーブルを作成します：

```bash
%%bash
mysql -uroot -proot -e "
DROP DATABASE IF EXISTS utf8_db;
CREATE DATABASE utf8_db CHARACTER SET utf8 COLLATE utf8_general_ci;
USE utf8_db;
CREATE TABLE memo (
    id INT PRIMARY KEY AUTO_INCREMENT,
    content TEXT
) CHARACTER SET utf8 COLLATE utf8_general_ci;"
```

次に、絵文字を含むデータをINSERTしてみましょう：

```bash
%%bash
mysql -uroot -proot utf8_db -e "
INSERT INTO memo (content) VALUES ('寿司🍣が好きです');
"
```

**結果：エラーが発生！**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/5d3d507e-b619-4b79-8039-22a307d40cff.png)

```
ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x8D\xA3...' for column 'content' at row 1
```

このエラーの意味は、「🍣」という絵文字が4バイトの文字であり、3バイトまでしか対応していない`utf8`では保存できないということです。

### 【成功編】utf8mb4テーブルなら🍣も保存できる

今度は`utf8mb4`でデータベースを作成してみましょう：

```bash
%%bash
mysql -uroot -proot -e "
DROP DATABASE IF EXISTS utf8mb4_db;
CREATE DATABASE utf8mb4_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE utf8mb4_db;
CREATE TABLE memo (
    id INT PRIMARY KEY AUTO_INCREMENT,
    content TEXT
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
INSERT INTO memo (content) VALUES ('寿司🍣が好きです');
"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/6e1c9c11-206d-45e1-9d53-3e427acf6aa4.png)

今度はエラーが発生せず、正常にINSERTが完了します！

### 【表示確認】Python で接続・SELECT

Pythonから接続して、正しく保存されているかを確認してみましょう：

```python
%pip install -q mysql-connector-python

import mysql.connector
import pandas as pd

conn = mysql.connector.connect(
    host="localhost",
    user="root",
    password="root",
    database="utf8mb4_db",
    charset="utf8mb4"
)

df = pd.read_sql("SELECT * FROM memo;", conn)
print(df)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/bc9241fd-e4bd-4f7b-80f1-96b9b2f5c2c8.png)

**結果**：正しく「寿司🍣が好きです」が表示されます！

## 【応用編】接続側をutf8にしてSELECTすると文字化け

データベース側は`utf8mb4`でも、接続時の文字コード設定が`utf8`だと問題が起こります：

```bash
%%bash
mysql -uroot -proot utf8mb4_db << 'EOF'
SET NAMES utf8;
SELECT content FROM memo;
EOF
"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/70cda9fc-f0e6-40df-b9b0-14d8d0f1a611.png)

この場合、絵文字部分が文字化けしたり、正しく表示されない可能性があります。

## よくある誤解とチェックポイント

MySQLにおける文字コード設定には、いくつかの誤解が存在します。たとえば、utf8という指定が完全なUTF-8だと思われがちですが、実際には最大3バイトまでの制限があり、絵文字など一部の文字を正しく扱えません。

また、文字化けはINSERT時だけでなく、SELECT時の接続設定（たとえば SET NAMES）が正しくない場合にも発生することがあります。さらに、「utf8mb4は特殊な用途向け」と誤解されがちですが、現在ではあらゆる用途においてutf8mb4を使用するのが推奨されています。

### チェックポイント

以下の設定をすべて`utf8mb4`に統一することが重要です：

1. **データベース作成時**
   ```sql
   CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
   ```

2. **テーブル作成時**
   ```sql
   CREATE TABLE mytable (...) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
   ```

3. **接続時**（Python例）
   ```python
   conn = mysql.connector.connect(
       ...,
       charset="utf8mb4"
   )
   ```

4. **MySQL設定ファイル**（my.cnf）
   ```ini
   [mysql]
   default-character-set = utf8mb4
   
   [mysqld]
   character-set-server = utf8mb4
   ```

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/3d6e4e1b-e786-41bb-b6d3-36c60356b866.png)

MySQLで日本語や絵文字などを安全に扱うには、文字コードの選択が非常に重要です。MySQLのutf8は最大3バイトまでの文字しか扱えないため、絵文字や一部の旧漢字など、4バイトの文字を保存することができません。このため、より広範な文字に対応したutf8mb4の利用が現在では推奨されています。

データベース、テーブル、接続設定など、すべての箇所でutf8mb4を統一して使うことで、文字化けや保存エラーといったトラブルを防ぐことができます。

この記事で紹介したサンプルは、Google Colab上ですぐに試せる内容となっており、実際に手を動かすことで、文字コードに関する問題点を具体的に理解できるはずです。

絵文字や多言語対応が求められるシーンが増えている今、utf8mb4の採用は非常に有効な選択肢です。ただし、時代の流れとともに文字コードやMySQLの仕様も変化していく可能性があるため、最新のMySQL公式ドキュメントも確認することをおすすめします。

これからMySQLを使って新しいアプリケーションを開発する際には、安全で柔軟なデータベース設計を意識しつつ、utf8mb4の利用を前向きに検討してみてください。

## 参考情報

https://dev.mysql.com/doc/refman/8.4/en/charset.html

https://support.huaweicloud.com/eu/trouble-gaussdbformysql/gaussdbformysql_trouble_0204.html

https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-utf8mb4.html

https://dev.mysql.com/doc/refman/8.4/en/charset.html