UTF-8 dan Unicode Mengapa Emoji Itu Menghancurkan Database Anda
Aplikasi Anda memasukkan emoji dan MySQL melemparkan pesan 'Nilai string salah'. Berikut alasannya — titik kode vs byte, kebohongan MySQL utf8 vs utf8mb4, pasangan surrogat JavaScript, dan cara memperbaikinya secara benar.
Aplikasi Anda bekerja dengan baik. Lalu seorang pengguna mengetikkan emoji di bidang teks, dan MySQL melemparkan Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio'. Atau mungkin emoji itu menghilang secara diam-diam. Atau seluruh INSERT gagal dan Anda kehilangan data. Semuanya karena karakter empat byte yang tidak diharapkan oleh kolom database Anda.
Ini bukan kekurangan MySQL atau bug PHP. Ini merupakan konsekuensi dari cara Unicode → pengkodean UTF-8 bekerja, dan setelah Anda memahaminya, Anda tidak akan terkejut lagi.
Kode titik vs byte: perbedaan sebenarnya
Unicode mengalokasikan setiap karakter dengan kode titik — sebuah angka. Huruf A adalah U+0041. Tanda Euro adalah U+20AC. Emoji 😀 adalah U+1F600. Itu adalah identitas abstrak karakter tersebut.
UTF-8 adalah pengkodean — cara menyimpan kode titik sebagai byte. Triknya adalah bahwa UTF-8 memiliki lebar variabel: ia menggunakan 1 hingga 4 byte tergantung pada nilai kode titik. Ini adalah cara agar UTF-8 tetap kompatibel dengan ASCII (semua karakter ASCII menggunakan 1 byte dalam UTF-8) sambil juga mengkodekan setiap karakter yang ada.
Aturan pengkodean:
- U+0000 hingga U+007F (ASCII) → 1 byte
- U+0080 hingga U+07FF (Latin diperluas, Arab, Hebrui, dll.) → 2 byte
- U+0800 hingga U+FFFF (sebagian besar CJK, tanda baca, simbol) → 3 byte
- U+10000 hingga U+10FFFF (emoji, skrip langka, simbol matematika) → 4 byte
Inilah sebabnya emoji 😀 (U+1F600) membutuhkan 4 byte: kode titiknya di atas U+FFFF.
Ukuran byte UTF-8: tabel referensi
Berikut ini adalah biaya aktual karakter umum dalam byte:
| Karakter | Keterangan | Titik Kode Unicode | Byte UTF-8 (hex) | Jumlah Byte |
|---|---|---|---|---|
| A | Huruf Latin kapital A | U+0041 | 41 | 1 |
| é | Huruf e dengan tanda akut | U+00E9 | C3 A9 | 2 |
| € | Tanda Euro | U+20AC | E2 82 AC | 3 |
| 中 | Karakter Cina "tengah" | U+4E2D | E4 B8 AD | 3 |
| 😀 | Emoji senyum | U+1F600 | F0 9F 98 80 | 4 |
| 🔥 | Emoji api | U+1F525 | F0 9F 94 A5 | 4 |
| 𝕳 | Huruf Fraktur Matematika H | U+1D573 | F0 9D 95 B3 | 4 |
Untuk memverifikasi ini sendiri, gunakan Kalkulator Panjang String — ini menampilkan jumlah karakter dan jumlah byte untuk teks apa pun yang Anda tempelkan. Tempel 😀 dan Anda akan melihat 1 karakter tetapi 4 byte.
Kebodohan charset MySQL
Di sini para pengembang terjebak. MySQL memiliki charset yang disebut utf8. Terdengar benar. Tapi salah — charset MySQL utf8 hanya mendukung hingga 3 byte urutan. Emoji (4 byte) tidak didukung.
Charset UTF-8 penuh dalam MySQL adalah utf8mb4 (diperkenalkan di MySQL 5.5.3, dirilis tahun 2010). Jika kolom Anda menggunakan utf8 dan seseorang memasukkan emoji, MySQL akan secara diam-diam memotong data atau melemparkan:
Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio' at row 1
Solusi:
-- Convert the table
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Or for a specific column
ALTER TABLE users MODIFY bio TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- And set your connection charset
SET NAMES utf8mb4;
Juga perbarui konfigurasi koneksi database aplikasi Anda. Di MySQL PDO:
$pdo = new PDO($dsn, $user, $pass, [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]);
Jebakan VARCHAR(255)
VARCHAR(255) di MySQL berarti 255 karakter, bukan 255 byte — tetapi batas penyimpanan untuk satu baris dihitung dalam byte. Dengan utf8mb4, setiap karakter bisa mencapai hingga 4 byte, sehingga kolom tersebut menjamin hingga 1.020 byte. Ini penting ketika Anda menggunakan batas indeks default InnoDB (767 byte) untuk kolom varchar: VARCHAR(255) Masalah pasangan surrogate di JavaScript
-- This fails on older MySQL with default innodb_large_prefix=OFF
CREATE INDEX idx_email ON users (email); -- email is VARCHAR(255) utf8mb4
-- Fix: use a prefix index
CREATE INDEX idx_email ON users (email(191)); -- 191 * 4 = 764 bytes, under 767
-- Or enable large prefixes (MySQL 5.7+, on by default in 8.0)
-- Set innodb_large_prefix = ON in my.cnf
JavaScript menggunakan UTF-16 secara internal, bukan UTF-8. Dan UTF-16 memiliki pengkodean multi-unit sendiri untuk kode titik di atas U+FFFF:
pasangan surrogate — dua unit kode 16-bit yang bersama-sama mewakili satu karakter. Ini berarti
di JavaScript menghitung unit kode UTF-16, bukan karakter: String.length Untuk operasi string yang membutuhkan kesadaran karakter, gunakan operator penyebaran atau
'😀'.length // → 2 (two UTF-16 surrogate code units)
[...'😀'].length // → 1 (spread operator uses Unicode code points)
// Checking the character at index 0
'😀'[0] // → '\uD83D' (the high surrogate, not the emoji)
'😀'.codePointAt(0) // → 128512 (0x1F600, correct)
Contoh emoji keluarga layak dipertimbangkan. Intl.Segmenter:
// Count actual grapheme clusters
const segmenter = new Intl.Segmenter();
const chars = [...segmenter.segment('👨👩👧👦')];
chars.length // → 1 (family emoji is one grapheme cluster)
'👨👩👧👦'.length // → 11 (UTF-16 code units)
terdiri dari empat emoji yang dihubungkan oleh Zero Width Joiners (U+200D). Sebuah pendekatan yang tidak cermat 👨👩👧👦 menghasilkan 11. Jumlah cluster grafem aktual: 1. Ini penting jika Anda mengimplementasikan batas karakter — batas yang didasarkan pada .length akan berperilaku tidak sesuai ketika pengguna mengetik urutan emoji. String.length Cara memeriksa pengkodean secara praktis
di PHP menghitung byte, bukan karakter. Ini sering menangkap pengembang PHP saat berurusan dengan string multi-byte — string 10 emoji akan melaporkan panjang 40. Gunakan
Ular piton
s = '😀'
print(len(s)) # 1 (Python 3 counts code points)
print(len(s.encode('utf-8'))) # 4 bytes
print(s.encode('utf-8').hex()) # f09f9880
PHP
$s = '😀';
echo strlen($s); // 4 (bytes, not characters)
echo mb_strlen($s); // 1 (characters)
echo mb_strlen($s, '8bit'); // 4 (bytes, explicit)
strlen() ketika Anda peduli terhadap jumlah karakter. mb_strlen() Pemeriksaan cepat
MySQL
-- Check charset of a table
SHOW CREATE TABLE users\G
-- Check charset of a specific column
SELECT CHARACTER_SET_NAME, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'users' AND COLUMN_NAME = 'bio';
-- Character count vs byte count
SELECT CHAR_LENGTH('😀'), LENGTH('😀');
-- → 1, 4
Jika Anda ingin melihat perbedaan antara byte dan karakter untuk teks apa pun tanpa menulis kode,
mengatasinya secara instan — tempelkan teks apa pun dan itu menampilkan jumlah karakter, jumlah kata, dan jumlah byte secara bersamaan. Kalkulator Panjang String Daftar pemeriksaan bug pengkodean
Charset MySQL:
- Apakah itu ? Periksa dengan
utf8mb4, bukanutf8Koneksi MySQL:SHOW CREATE TABLE. - Apakah aplikasi Anda mengirimkan ? Periksa DSN atau konfigurasi koneksi Anda.
SET NAMES utf8mb4PHP strlen vs mb_strlen: - Apakah Anda menggunakan fungsi penghitungan byte di mana Anda membutuhkan penghitungan karakter? JavaScript .length:
- Apakah Anda menghitung unit kode di mana Anda membutuhkan cluster grafem? Header HTTP:
- Apakah respons Anda mengirimkan File pengkodean:
Content-Type: text/html; charset=utf-8? - Apakah file sumber dan dump SQL disimpan sebagai UTF-8 tanpa BOM? UTF-8 dan Unicode: Mengapa Emoji Itu Menghancurkan Database Anda 2
Instal Ekstensi Kami
Tambahkan alat IO ke browser favorit Anda untuk akses instan dan pencarian lebih cepat
恵 Papan Skor Telah Tiba!
Papan Skor adalah cara yang menyenangkan untuk melacak permainan Anda, semua data disimpan di browser Anda. Lebih banyak fitur akan segera hadir!
Alat Wajib Coba
Lihat semua Pendatang baru
Lihat semuaMemperbarui: Kita alat terbaru ditambahkan pada 20 Juni 2026
