Tipe Numerik

TINYINT sampai BIGINT, UNSIGNED, dan penggunaan DECIMAL untuk nilai uang yang presisi.

Daftar isi

Tipe numerik menentukan cara MySQL menyimpan bilangan, batas nilai yang diterima, tingkat presisinya, serta reaksi server ketika nilai yang dimasukkan melewati batas kolom. Topik ini membahas lima tipe bilangan bulat dari TINYINT sampai BIGINT, atribut UNSIGNED yang menggeser rentang ke nilai non-negatif, serta pemilihan antara DECIMAL dan DOUBLE untuk nilai uang. Setiap perilaku didemonstrasikan pada MySQL 8.0 lewat klien mysql, termasuk error yang muncul ketika nilai di luar rentang dimasukkan dalam strict mode. Hasil akhirnya adalah dasar pemilihan tipe yang berpijak pada perilaku nyata, bukan pada perkiraan.

Keluarga tipe bilangan bulat#

MySQL menyediakan lima tipe bilangan bulat, yaitu TINYINT, SMALLINT, MEDIUMINT, INT, dan BIGINT. Kelimanya menyimpan bilangan tanpa bagian pecahan dan berbeda hanya pada jumlah byte penyimpanan serta rentang nilainya. Tabel berikut merangkum rentang bertanda (signed) dan tanpa tanda (unsigned) setiap tipe menurut manual MySQL 8.0.

TipePenyimpananRentang bertandaRentang tanpa tanda
TINYINT1 byte-128 sampai 1270 sampai 255
SMALLINT2 byte-32768 sampai 327670 sampai 65535
MEDIUMINT3 byte-8388608 sampai 83886070 sampai 16777215
INT4 byte-2147483648 sampai 21474836470 sampai 4294967295
BIGINT8 byte-9223372036854775808 sampai 92233720368547758070 sampai 18446744073709551615

Prinsip pemilihannya adalah mengambil tipe terkecil yang rentangnya masih menampung nilai terburuk yang mungkin, dengan ruang untuk pertumbuhan data. Kolom nilai ujian dengan rentang 0 sampai 100 cukup memakai TINYINT, sedangkan penghitung baris pada tabel besar layak memakai BIGINT karena INT berhenti pada 2147483647. Tipe INT sering menjadi pilihan bawaan, tetapi ukuran yang tidak diperhitungkan hanya menunda masalah, bukan menghilangkannya. Apabila ada keraguan antara dua tipe, perkiraan kebutuhan data sampai sepuluh tahun ke depan menjadi penentu yang lebih aman daripada kebiasaan menyalin skema lama.

Atribut UNSIGNED#

Atribut UNSIGNED diletakkan setelah nama tipe untuk melarang nilai negatif dan menggeser seluruh rentang ke bilangan tak negatif, sehingga TINYINT UNSIGNED menerima 0 sampai 255 alih-alih -128 sampai 127. Atribut ini tepat untuk kolom yang secara semantik memang tidak mungkin negatif, seperti nilai ujian, jumlah stok, atau durasi. Demonstrasi berikut membuat tabel nilai ujian dengan kolom bertipe TINYINT UNSIGNED, mengisinya dengan tiga baris, kemudian mencoba memasukkan nilai 300 yang berada di luar rentang.

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

CREATE TABLE nilai_ujian (
  siswa VARCHAR(40) NOT NULL,
  nilai TINYINT UNSIGNED NOT NULL
);

INSERT INTO nilai_ujian VALUES
  ('Ali', 88),
  ('Budi', 74),
  ('Citra', 95);
bash
# membuat ulang basis data latihan dan tabelnya
mysql -u root < skema-nilai.sql

Sisipan dengan nilai 300 menimbulkan error, bukan penyimpanan yang diam-diam dipotong.

bash
# mencoba memasukkan nilai di luar rentang TINYINT UNSIGNED
mysql -u root latihan_tipe -e "INSERT INTO nilai_ujian VALUES ('Dewi', 300);"
text Keluaran
ERROR 1264 (22003) at line 1: Out of range value for column 'nilai' at row 1

MySQL 8.0 menjalankan strict mode secara default, yakni mode server yang menolak data tidak sah dengan error 1264 seperti di atas. Pada konfigurasi lama tanpa mode strict, nilai yang sama dipotong ke 255 dan hanya memunculkan warning yang mudah terlewat. Data yang tersimpan tetap konsisten, sehingga pernyataan berikutnya dapat menampilkan isinya.

bash
# menampilkan isi tabel nilai_ujian
mysql -t -u root latihan_tipe -e "SELECT * FROM nilai_ujian;"
text Keluaran
+-------+-------+
| siswa | nilai |
+-------+-------+
| Ali   |    88 |
| Budi  |    74 |
| Citra |    95 |
+-------+-------+

DECIMAL untuk nilai yang presisi#

Tipe DOUBLE menyimpan bilangan pecahan dalam format biner IEEE 754 dan kinerjanya cepat. Namun, banyak nilai desimal yang tidak dapat dinyatakan tepat dalam biner, sehingga hasil aritmetikanya mendekati nilai yang benar tanpa pernah menyentuhnya persis. Tipe DECIMAL(M,D) berbeda, yaitu MySQL menyimpannya sebagai deretan digit dengan M digit total dan D digit di belakang tanda desimal, sehingga aritmetikanya eksak. Untuk nilai uang, selisih kecil yang menumpuk pada ribuan operasi bukan hal yang dapat diterima, sehingga DECIMAL menjadi pilihan bawaan untuk kolom moneter.

Demonstrasi berikut membuat tabel cicilan dengan dua kolom bernilai sama, satu bertipe DECIMAL(6,2) dan satu bertipe DOUBLE, lalu menjumlahkan sepuluh baris senilai 0.10 pada masing-masing kolom.

bash
# membuat tabel cicilan dan menjumlahkan sepuluh nilai 0.10
mysql -t -u root latihan_tipe <<'SQL'
CREATE TABLE cicilan (
  ke INT PRIMARY KEY,
  nilai_desimal DECIMAL(6,2) NOT NULL,
  nilai_ganda DOUBLE NOT NULL
);
INSERT INTO cicilan VALUES
  (1,0.10,0.10),(2,0.10,0.10),(3,0.10,0.10),(4,0.10,0.10),(5,0.10,0.10),
  (6,0.10,0.10),(7,0.10,0.10),(8,0.10,0.10),(9,0.10,0.10),(10,0.10,0.10);
SELECT SUM(nilai_desimal) AS total_desimal, SUM(nilai_ganda) AS total_ganda
FROM cicilan;
SQL
text Keluaran
+---------------+--------------------+
| total_desimal | total_ganda        |
+---------------+--------------------+
|          1.00 | 0.9999999999999999 |
+---------------+--------------------+

Kolom DECIMAL menghasilkan 1.00 persis, sedangkan kolom DOUBLE menghasilkan 0.9999999999999999. Selisih sebesar ini mungkin tampak sepele, tetapi pola yang sama muncul pada setiap penjumlahan bilangan yang tidak dapat dinyatakan dalam biner. Ekspresi literal berikut menunjukkan perbandingan yang sering dikutip, yaitu penjumlahan 0.1 dengan 0.2, beserta pembulatan nilai 10.105 ke DECIMAL(8,2).

bash
# membandingkan aritmetika desimal dan biner lalu menguji pembulatan
mysql -t -u root -e "SELECT 0.1 + 0.2 AS desimal, 0.1e0 + 0.2e0 AS ganda; SELECT CAST(10.105 AS DECIMAL(8,2)) AS dibulatkan;"
text Keluaran
+---------+---------------------+
| desimal | ganda               |
+---------+---------------------+
|     0.3 | 0.30000000000000004 |
+---------+---------------------+
+------------+
| dibulatkan |
+------------+
|      10.11 |
+------------+

Akar perbedaannya terletak pada representasi. Literal 0.1 tanpa eksponen diperlakukan sebagai DECIMAL dan dihitung dengan aritmetika desimal, sedangkan 0.1e0 dengan eksponen diperlakukan sebagai DOUBLE. Pembulatan CAST menjadikan 10.105 sebagai 10.11 karena kolom tujuan hanya menampung dua digit pecahan. Ketika kolom memang dideklarasikan sebagai desimal eksak, perilaku pembulatan seperti ini mudah diprediksi dan dapat diuji.

Kesalahan umum#

Kesalahan pada pemilihan tipe numerik jarang terasa saat tabel dibuat dan baru muncul ketika data bertumpuk. Tiga kesalahan berikut paling sering ditemukan beserta perbaikannya.

  1. Menyimpan nilai uang di DOUBLE. Penyebabnya adalah kebiasaan memilih tipe pecahan yang paling umum tanpa mempertimbangkan presisi, sehingga penjumlahan seperti pada bagian sebelumnya menghasilkan angka yang bergeser sedikit dan perbandingan kesetaraan menjadi tidak dapat diandalkan. Perbaikannya adalah memindahkan kolom moneter ke DECIMAL dengan skala yang sesuai kebutuhan, misalnya DECIMAL(12,2).
  2. Mengandalkan UNSIGNED untuk mencegah hasil negatif dari perhitungan. Pengurangan yang menghasilkan nilai di bawah nol pada ekspresi UNSIGNED memunculkan error 1690, seperti pada perintah berikut.
bash
# mencoba mengurangi nilai UNSIGNED melewati nol
mysql -u root latihan_tipe -e "SELECT CAST(0 AS UNSIGNED) - 1;"
text Keluaran
ERROR 1690 (22003) at line 1: BIGINT UNSIGNED value is out of range in '(cast(0 as unsigned) - 1)'

Perbaikannya bukan menghapus atribut UNSIGNED, melainkan memastikan logika aplikasi tidak pernah mengurangkan melewati nol, atau menggunakan tipe bertanda bila nilai negatif memang mungkin terjadi.

  1. Menganggap TINYINT(1) sebagai tipe boolean tersendiri. Angka di dalam tanda kurung hanyalah lebar tampilan dan tidak mengubah rentang maupun penyimpanan, sehingga TINYINT(1) tetap menyimpan bilangan bulat -128 sampai 127. Sejak MySQL 8.0.17 lebar tampilan untuk tipe bilangan bulat berstatus deprecated, sehingga kebiasaan menuliskannya sebaiknya ditinggalkan. Konvensi yang lebih jelas adalah TINYINT UNSIGNED dengan nilai 0 dan 1, atau mengandalkan konversi yang dilakukan driver aplikasi.

Ringkasan#

  • Lima tipe bilangan bulat MySQL berbeda pada penyimpanan dan rentang, dari TINYINT satu byte sampai BIGINT delapan byte, sehingga tipe terkecil yang menampung kebutuhan adalah pilihan yang tepat.
  • Atribut UNSIGNED melarang nilai negatif dan menggandakan rentang atas, tetapi bukan pengganti validasi aplikasi.
  • Dalam strict mode bawaan MySQL 8.0, nilai di luar rentang ditolak dengan error 1264, bukan dipotong diam-diam.
  • DECIMAL(M,D) menghitung secara eksak dan menjadi pilihan kolom uang, sedangkan DOUBLE menyimpan pendekatan biner yang membuat 0.1 plus 0.2 menghasilkan 0.30000000000000004.

Referensi#