Tipe String

CHAR versus VARCHAR, keluarga TEXT, serta dasar charset dan collation pada MySQL 8.

Daftar isi

Tipe string menentukan cara MySQL menyimpan teks, seberapa banyak ruang yang disiapkan, dan bagaimana dua teks dibandingkan. Topik ini membahas perbedaan CHAR dan VARCHAR, keluarga tipe TEXT, serta dasar character set dan collation yang mengatur pengodean dan aturan perbandingan pada MySQL 8.0. Semua perilaku diuji langsung dengan klien mysql, mulai dari penanganan spasi di akhir teks sampai perbedaan jumlah byte antar character set. Setelah topik ini selesai, pemilihan tipe teks untuk kolom baru memiliki dasar perilaku yang teruji, bukan sekadar kebiasaan.

CHAR dan VARCHAR#

CHAR(N) menyimpan teks dengan panjang tetap N karakter, yaitu MySQL mengisi sisa ruang dengan spasi saat menyimpan dan membuang spasi di akhir teks saat mengambilnya. VARCHAR(N) menyimpan teks sepanjang nilai yang dimasukkan ditambah satu atau dua byte untuk pencatat panjangnya, sehingga spasi di akhir teks tetap utuh. Karena perbedaan penanganan spasi itu, dua kolom yang menerima isi sama persis dapat mengembalikan panjang yang berbeda, seperti pada demonstrasi berikut.

sql skema-string.sql
-- tidak-dijalankan
DROP DATABASE IF EXISTS latihan_tipe;
CREATE DATABASE latihan_tipe;
USE latihan_tipe;

CREATE TABLE uji_spasi (
  huruf_c CHAR(6) NOT NULL,
  huruf_v VARCHAR(6) NOT NULL
);

INSERT INTO uji_spasi VALUES ('ab    ', 'ab    ');

CREATE TABLE anggota (
  id INT PRIMARY KEY,
  nama VARCHAR(40) NOT NULL
);

INSERT INTO anggota VALUES
  (1, 'citra'),
  (2, 'Budi'),
  (3, 'Dewi');

CREATE TABLE arsip (
  id INT PRIMARY KEY,
  ringkasan VARCHAR(255) NOT NULL,
  isi TEXT NOT NULL
);

INSERT INTO arsip VALUES
  (1, 'Laporan Januari', 'Isi lengkap laporan bulan Januari dengan ribuan karakter teks.');
bash
# membuat ulang basis data latihan beserta tabelnya
mysql -u root < skema-string.sql
bash
# membandingkan panjang hasil penyimpanan CHAR dan VARCHAR
mysql -t -u root latihan_tipe -e "SELECT huruf_c, huruf_v, CHAR_LENGTH(huruf_c) AS panjang_c, CHAR_LENGTH(huruf_v) AS panjang_v FROM uji_spasi;"
text Keluaran
+---------+---------+-----------+-----------+
| huruf_c | huruf_v | panjang_c | panjang_v |
+---------+---------+-----------+-----------+
| ab      | ab      |         2 |         6 |
+---------+---------+-----------+-----------+

Nilai yang dimasukkan adalah ab diikuti empat spasi, sehingga panjang aslinya enam karakter. Kolom CHAR(6) mengembalikan ab dengan panjang dua karena spasi di akhir teks dibuang saat pengambilan, sedangkan kolom VARCHAR(6) mengembalikan keenam karakter termasuk spasinya. Batas N pada keduanya dihitung dalam karakter, bukan byte, sehingga VARCHAR(40) mampu menampung empat puluh karakter apa pun termasuk yang multibyte. Pemilihan antara keduanya cukup mengikuti sifat data, yaitu teks dengan panjang nyaris tetap seperti kode negara cocok di CHAR, sedangkan teks yang panjangnya bervariasi lebih hemat pada VARCHAR.

Keluarga TEXT#

Empat tipe TEXT, yaitu TINYTEXT, TEXT, MEDIUMTEXT, dan LONGTEXT, menyimpan teks panjang dengan batas 255, 65535, 16777215, dan 4294967295 byte. Nilai teks yang panjang tidak lagi muat dalam halaman baris InnoDB, sehingga bagian teksnya disimpan terpisah dari bagian utama baris dan kolom tersebut hanya menyimpan penunjuknya. Perbedaan terpenting bagi pemula ada pada indeks, yaitu kolom TEXT wajib memakai panjang prefiks saat diindeks, sedangkan VARCHAR dapat diindeks penuh sesuai batas panjangnya. Demonstrasi berikut menampilkan arsip cadangan dengan ringkasan VARCHAR(255) dan isi TEXT, kemudian mengambil dua puluh karakter pertama dari isinya dengan fungsi LEFT.

bash
# menampilkan ringkasan dan awal isi arsip
mysql -t -u root latihan_tipe -e "SELECT id, ringkasan, LEFT(isi, 20) AS awal_isi FROM arsip;"
text Keluaran
+----+-----------------+----------------------+
| id | ringkasan       | awal_isi             |
+----+-----------------+----------------------+
|  1 | Laporan Januari | Isi lengkap laporan  |
+----+-----------------+----------------------+

Ukuran batas pada keluarga TEXT dinyatakan dalam byte, sedangkan batas VARCHAR(N) dinyatakan dalam karakter. Teks bahasa Indonesia yang murni ASCII menempati satu byte per karakter. Character set multibyte seperti utf8mb4 memakai lebih dari satu byte untuk sebagian karakter, sehingga batas byte dan batas karakter dapat berbeda jauh pada teks yang sama. Perbedaan ini membawa pembahasan ke character set dan collation pada bagian berikutnya.

Character set dan collation#

Character set adalah himpunan simbol beserta cara menyandinya menjadi byte, sedangkan collation adalah aturan yang menentukan urutan dan perbandingan karakter pada character set tersebut. MySQL 8.0 menjadikan utf8mb4 sebagai character set default server dengan collation default utf8mb4_0900_ai_ci. Perintah berikut menunjukkan konfigurasi bawaan server di sandbox ini.

bash
# membaca character set dan collation default server
mysql -t -u root -e "SELECT @@character_set_server AS character_set_server, @@collation_server AS collation_server;"
text Keluaran
+----------------------+--------------------+
| character_set_server | collation_server   |
+----------------------+--------------------+
| utf8mb4              | utf8mb4_0900_ai_ci |
+----------------------+--------------------+

Nama utf8mb4 layak dicermati karena MySQL menyimpan sampai empat byte per karakter pada character set ini, sehingga seluruh karakter Unicode termasuk emoji dapat tersimpan. Character set lawas seperti latin1 menyandikan karakter Eropa Barat dengan satu byte. Demonstrasi berikut membuat dua tabel berisi teks résumé, satu dengan character set latin1 dan satu dengan utf8mb4, lalu membandingkan jumlah byte hasil penyimpanannya.

bash
# membandingkan jumlah byte teks yang sama pada dua character set
mysql -t -u root latihan_tipe <<'SQL'
CREATE TABLE catatan_lama (
  isi CHAR(10) CHARACTER SET latin1 NOT NULL
) CHARSET latin1;
CREATE TABLE catatan_baru (
  isi CHAR(10) CHARACTER SET utf8mb4 NOT NULL
) CHARSET utf8mb4;
INSERT INTO catatan_lama VALUES ('résumé');
INSERT INTO catatan_baru VALUES ('résumé');
SELECT isi, LENGTH(isi) AS byte_latin1 FROM catatan_lama;
SELECT isi, LENGTH(isi) AS byte_utf8mb4 FROM catatan_baru;
SQL
text Keluaran
+----------+-------------+
| isi      | byte_latin1 |
+----------+-------------+
| résumé   |           6 |
+----------+-------------+
+----------+--------------+
| isi      | byte_utf8mb4 |
+----------+--------------+
| résumé   |            8 |
+----------+--------------+

Fungsi LENGTH menghitung byte, sedangkan CHAR_LENGTH menghitung karakter, dan teks résumé memuat dua karakter é yang menempati satu byte pada latin1 tetapi dua byte pada utf8mb4. Karakter di luar jangkauan latin1 bahkan ditolak, seperti emoji pada percobaan berikut.

bash
# mencoba menyimpan emoji ke kolom latin1
mysql -u root latihan_tipe -e "INSERT INTO catatan_lama VALUES ('hujan ☔');"
text Keluaran
ERROR 1366 (HY000) at line 1: Incorrect string value: '\xE2\x98\x94' for column 'isi' at row 1

Server menolak nilai tersebut dengan error 1366 karena karakter ☔ tidak ada dalam latin1, sedangkan pada kolom utf8mb4 karakter yang sama tersimpan tanpa penyesuaian apa pun. Collation kemudian menentukan perbandingan, yaitu collation yang berakhiran _ci bersifat case insensitive sehingga huruf besar dan kecil dianggap sama. Pencarian berikut menemukan baris citra meskipun pola pencariannya Citra.

bash
# mencari nama tanpa membedakan huruf besar dan kecil
mysql -t -u root latihan_tipe -e "SELECT id, nama FROM anggota WHERE nama = 'Citra';"
text Keluaran
+----+-------+
| id | nama  |
+----+-------+
|  1 | citra |
+----+-------+

Kesalahan umum#

Pemilihan tipe teks tanpa pertimbangan menimbulkan biaya yang baru terasa setelah aplikasi berjalan. Tiga kesalahan berikut paling sering ditemukan beserta cara memperbaikinya.

  1. Memakai VARCHAR(255) untuk semua kolom teks. Angka 255 bukan hasil analisis, melainkan kebiasaan yang tersalin dari skema ke skema. Kolom nama dengan isi realistis sampai 60 karakter lebih jelas dinyatakan sebagai VARCHAR(60) karena batas yang jujur ikut mendokumentasikan semantik kolom. Perbaikannya adalah menghitung batas dari data terpanjang yang wajar, lalu menyisakan ruang pertumbuhan.
  2. Mengindeks kolom TEXT tanpa panjang prefiks. Server menolak deklarasi indeks semacam itu dengan error 1170, seperti pada percobaan berikut.
bash
# mencoba mengindeks kolom TEXT tanpa panjang prefiks
mysql -u root latihan_tipe -e "CREATE TABLE uji_indeks (isi TEXT, KEY (isi));"
text Keluaran
ERROR 1170 (42000) at line 1: BLOB/TEXT column 'isi' used in key specification without a key length

Perbaikannya adalah menyebut panjang prefiks, misalnya KEY (isi(20)), atau memangkas teks ke kolom VARCHAR apabila isi yang nyata memang pendek.

  1. Mengabaikan collation pada data yang sering dibandingkan. Pencarian pada collation utf8mb4_0900_ai_ci mengabaikan perbedaan huruf besar dan kecil, sehingga dua nilai seperti citra dan Citra dianggap sama dan tidak terdeteksi sebagai duplikat oleh indeks unik. Apabila pembedaan huruf diperlukan, gunakan collation yang berakhiran _bin atau _cs pada kolom tersebut.

Ringkasan#

  • CHAR(N) menyimpan teks dengan panjang tetap N karakter dan membuang spasi akhir saat pengambilan, sedangkan VARCHAR(N) menyimpan sepanjang isinya dan menjaga spasi akhir.
  • Keluarga TEXT menampung teks panjang sampai 4294967295 byte dan wajib panjang prefiks saat diindeks.
  • MySQL 8.0 memakai utf8mb4 dengan utf8mb4_0900_ai_ci sebagai default, sehingga seluruh karakter Unicode tersimpan dan perbandingan tidak membedakan huruf besar dan kecil.
  • LENGTH menghitung byte sedangkan CHAR_LENGTH menghitung karakter, dan keduanya berbeda pada teks multibyte.

Referensi#