Kita sudah menjelajahi integer, pecahan, string, waktu, boolean, JSON, hingga biner — masing-masing dengan rentang dan perilakunya. Sekarang tibalah pertanyaan yang sesungguhnya: ketika mendesain tabel nyata, tipe mana yang kita tulis untuk setiap kolom? Keputusan ini sulit diubah kemudian karena ALTER TABLE pada tabel besar memakan waktu dan risiko, dan tipe yang salah diam-diam memakan ruang, memperlambat query, atau gagal menyimpan data. Pada Chapter ini kita merangkum seluruh section menjadi satu kerangka keputusan berbentuk matriks, aturan praktis yang mudah diingat, dan skema toko kecil yang teruji dari CREATE TABLE sampai JOIN.
Matriks Keputusan Kasus ke Tipe #
Tabel berikut memetakan kasus kolom yang paling sering muncul langsung ke pilihan tipe yang tepat. Gunakan sebagai peta cepat saat menulis CREATE TABLE:
| Kasus kolom | Tipe yang dipilih | Alasan singkat |
|---|---|---|
| Harga, saldo, nominal uang | DECIMAL(10,2) s.d. DECIMAL(14,2) | Pecahan eksak; FLOAT/DOUBLE bisa melenceng |
| Jumlah, stok, kuantitas kecil | TINYINT / SMALLINT UNSIGNED | Non-negatif; pilih ukuran sesuai batas wajar |
| ID baris tabel | INT / BIGINT UNSIGNED AUTO_INCREMENT | Penomoran otomatis; BIGINT untuk tabel transaksi besar |
| Status dengan daftar tetap | ENUM(...) atau TINYINT UNSIGNED | Validasi gratis; ENUM terbaca manusia, TINYINT fleksibel |
| Ya/tidak | BOOLEAN | Alias TINYINT(1); bisa direkap dengan SUM() |
| Nama, judul, label pendek | VARCHAR(n) | Panjang variabel; tetapkan n yang wajar |
| Deskripsi, artikel, isi bebas | TEXT | Tanpa batas panjang yang perlu kita tebak |
| Tanggal lahir, jadwal, event masa lalu | DATE / DATETIME | Rentang sampai 9999; tidak terganggu zona waktu |
| Waktu dibuat/diubah server | TIMESTAMP | Otomatis UTC; hemat 4 byte; rentang 1970–2038 |
| Hasil pengukuran, koordinat | DOUBLE | Presisi 15 digit; galat biner tak masalah |
| Hash, token, byte tetap | BINARY(n) / VARBINARY(n) | Byte mentah; HEX()/UNHEX() untuk membaca |
| Atribut yang beragam antarbaris | JSON | Semi-terstruktur; tervalidasi server |
| Foto, lampiran | MEDIUMBLOB atau sistem file | BLOB bila harus atomik dengan barisnya |
Dua baris waktu pada matriks tersebut layak digarisbawahi. TIMESTAMP dipakai untuk kejadian yang dicatat server sendiri (login, insert, update) karena nilainya otomatis dikonversi ke UTC sehingga aman dipindah antarzona waktu. DATETIME dipakai untuk event yang merupakan fakta dunia nyata — jadwal kereta, tanggal lahir, jam mulai konser — karena nilai seperti “12 Oktober 2019 pukul 08:00 WIB” memang tidak boleh bergeser mengikuti zona waktu pembaca. Event masa lalu hampir selalu DATETIME karena TIMESTAMP tidak bisa menyimpan apa pun sebelum tahun 1970.
Aturan Praktis yang Mudah Diingat #
Matriks di atas bisa dipadatkan menjadi lima kalimat yang cukup menutupi mayoritas keputusan sehari-hari. Pertama, uang selalu DECIMAL — tanpa kecuali, sekecil apa pun nominalnya. Kedua, status kecil dan stabil memakai ENUM, sedangkan status yang mungkin bertambah memakai TINYINT dengan tabel referensi. Ketiga, teks yang panjangnya tidak bisa kita tebak memakai TEXT, bukan VARCHAR(99999). Keempat, waktu server TIMESTAMP, waktu dunia nyata DATETIME. Kelima, kolom ID memakai INT UNSIGNED AUTO_INCREMENT untuk tabel referensi (pelanggan, produk) dan BIGINT UNSIGNED AUTO_INCREMENT untuk tabel yang tumbuh paling cepat (pesanan, log), karena pesanan bisa mencapai miliaran baris lebih dulu.
Tiga prinsip tambahan menjaga kualitas skema. Pilih selalu tipe terkecil yang masih menampung nilai maksimal yang masuk akal — tabel besar membayar mahal setiap byte ekstra. Tambatkan atribut UNSIGNED pada kolom yang secara bisnis tidak pernah negatif supaya nilai jahat tertolak di gerbang. Dan jaga kolom relasi bertipe sama persis dengan kolom kunci yang dirujuknya — INT UNSIGNED berpasangan dengan INT UNSIGNED — supaya index bekerja optimal saat nanti kita pasang foreign key.
Contoh Skema Teruji: Toko Kecil #
Mari kita terapkan seluruh kerangka pada skema toko kecil dengan tiga tabel. Perhatikan bahwa setiap kolom kini punya alasan yang bisa kita jelaskan:
DROP DATABASE IF EXISTS belajar_tipe;
CREATE DATABASE belajar_tipe;
USE belajar_tipe;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE pelanggan (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(60) NOT NULL,
email VARCHAR(120),
aktif BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE produk (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(80) NOT NULL,
harga DECIMAL(12,2) NOT NULL,
stok SMALLINT UNSIGNED NOT NULL DEFAULT 0,
deskripsi TEXT
);
CREATE TABLE pesanan (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
pelanggan_id INT UNSIGNED NOT NULL,
produk_id INT UNSIGNED NOT NULL,
jumlah TINYINT UNSIGNED NOT NULL,
total DECIMAL(14,2) NOT NULL,
status ENUM('baru', 'dibayar', 'dikirim', 'selesai') NOT NULL DEFAULT 'baru',
dibuat_pada TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)Mari kita bedah keputusan tipe pada skema di atas:
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEYpadapelanggandanproduk: dua tabel referensi ini tumbuh lambat, jadiINT(maksimal 4,29 miliar) lebih dari cukup. KlausaAUTO_INCREMENTmembuat server mengisi nomor berikutnya otomatis saat kita INSERT tanpaid.id BIGINT UNSIGNED AUTO_INCREMENTpadapesanan: tabel transaksi tumbuh paling cepat — satu toko besar bisa mencatat ratusan ribu pesanan per hari — maka kita siapkan langit-langit 18 kuintiliun sejak awal.harga DECIMAL(12,2)dantotal DECIMAL(14,2): aturan uang.totaldibuat lebih lebar karena hasil kali harga × jumlah bisa melampaui harga satuannya.stok SMALLINT UNSIGNEDdanjumlah TINYINT UNSIGNED: stok per produk hingga 65.535 unit; jumlah barang dalam satu pesanan hampir pasti di bawah 255.status ENUM(...): empat nilai yang terkendali, plusDEFAULT 'baru'supaya pesanan baru otomatis berstatus benar.dibuat_pada TIMESTAMP DEFAULT CURRENT_TIMESTAMP: kejadian yang dicatat server sendiri — pas denganTIMESTAMP, dan nilai bawaannya terisi jam server saat INSERT.
Sekarang kita isi datanya. Kolom id sengaja tidak kita sebut pada INSERT sehingga penomoran otomatis bekerja, sementara dibuat_pada kita isi literal tetap agar contoh ini bisa diuji ulang kapan pun:
INSERT INTO pelanggan (nama, email, aktif) VALUES
('Budi Santoso', '[email protected]', TRUE),
('Siti Aminah', '[email protected]', TRUE);
INSERT INTO produk (nama, harga, stok, deskripsi) VALUES
('Kaos Polos Merah', 45000.00, 120, 'Katun combed 30s, ukuran S hingga XL.'),
('Topi Belajar', 27500.00, 85, 'Topi bordir logo, warna hitam.');
INSERT INTO pesanan (pelanggan_id, produk_id, jumlah, total, status, dibuat_pada) VALUES
(1, 2, 2, 55000.00, 'dikirim', '2026-01-15 10:05:00'),
(2, 1, 1, 45000.00, 'baru', '2026-01-16 08:30:00');
SELECT id, nama FROM pelanggan ORDER BY id;Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
+----+--------------+
| id | nama |
+----+--------------+
| 1 | Budi Santoso |
| 2 | Siti Aminah |
+----+--------------+
2 rows in set (0.00 sec)Kolom id pelanggan terisi 1 dan 2 otomatis — angka yang kita pakai sebagai pelanggan_id pada INSERT pesanan. Skema dan datanya kini siap dibaca lintas tabel:
SELECT p.nama AS pelanggan,
pr.nama AS produk,
ps.jumlah,
ps.total,
ps.status,
ps.dibuat_pada
FROM pesanan ps
JOIN pelanggan p ON p.id = ps.pelanggan_id
JOIN produk pr ON pr.id = ps.produk_id
ORDER BY ps.id;Output yang Dihasilkan:
+--------------+------------------+--------+----------+---------+---------------------+
| pelanggan | produk | jumlah | total | status | dibuat_pada |
+--------------+------------------+--------+----------+---------+---------------------+
| Budi Santoso | Topi Belajar | 2 | 55000.00 | dikirim | 2026-01-15 10:05:00 |
| Siti Aminah | Kaos Polos Merah | 1 | 45000.00 | baru | 2026-01-16 08:30:00 |
+--------------+------------------+--------+----------+---------+---------------------+
2 rows in set (0.00 sec)Query JOIN tersebut berhasil dibaca dari tiga tabel — kolom id di tiap tabel saling terhubung lewat pelanggan_id dan produk_id yang bertipe sama persis dengan kunci yang dirujuknya. Tipe data yang seragam di kedua sisi relasi membuat pembanding p.id = ps.pelanggan_id sederhana dan efisien; pemasangan foreign key formalnya akan kita bahas pada Section Table Design.
Terakhir, kita bereskan database latihan:
DROP DATABASE belajar_tipe;Output yang Dihasilkan:
Query OK, 3 rows affected (0.00 sec)Referensi #
- Data Types — MySQL 8.4 Reference Manual
- Data Type Storage Requirements — MySQL 8.4 Reference Manual
- Using AUTO_INCREMENT — MySQL 8.4 Reference Manual
Kerangka keputusan kini lengkap di tangan kita. Satu topik kecil tapi berdampak besar masih tersisa: atribut SIGNED/UNSIGNED — bagaimana membaca rentangnya dan mengapa pengurangan sederhana pada kolom UNSIGNED bisa memicu galat yang mengejutkan. Chapter penutup section ini membedahnya tuntas.