Sejauh ini seluruh data yang kita buat di program Go hidup di memori: begitu proses berhenti, data lenyap bersama proses tersebut. Aplikasi nyata justru sebaliknya — daftar pengguna, riwayat pesanan, dan log transaksi harus bertahan lama dan dibaca banyak program sekaligus. Kebutuhan itulah yang diselesaikan database, dan model paling dominan di industri adalah database relasional seperti PostgreSQL, MySQL, dan SQLite. Sebelum menyentuh kode Go, kita perlu memahami cara database relasional menyusun data dan bagaimana bahasa SQL membaca serta mengubahnya.
Tabel, Baris, dan Kolom #
Database relasional menyimpan data dalam tabel. Satu tabel mewakili satu jenis entitas — misalnya tabel pengguna untuk seluruh pengguna aplikasi. Tabel terdiri dari kolom yang jumlahnya tetap dan baris yang jumlahnya tumbuh mengikuti data. Setiap kolom punya tipe data: umur hanya menerima bilangan bulat, aktif hanya menerima benar atau salah. Struktur tabel dideklarasikan dalam SQL (Structured Query Language):
CREATE TABLE pengguna (
id SERIAL PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
umur INTEGER,
aktif BOOLEAN DEFAULT true
);Setelah diisi, isi tabel terlihat seperti lembar data berikut:
| id | nama | umur | aktif | |
|---|---|---|---|---|
| 1 | Budi | [email protected] | 25 | true |
| 2 | Siti | [email protected] | 22 | true |
Satu baris mewakili satu entitas; satu kolom mewakili satu atribut. Struktur kaku semacam ini memungkinkan server memvalidasi tipe dan mengoptimalkan akses data secara seragam.
Primary Key dan Foreign Key #
Setiap baris butuh identitas unik agar bisa dirujuk pasti. Primary key (PK) adalah kolom yang nilainya unik dan tidak boleh kosong untuk setiap baris — pada tabel di atas, kolom id. Foreign key (FK) adalah kolom yang merujuk ke primary key tabel lain, membentuk relasi: satu pengguna memiliki banyak pesanan, tetapi setiap pesanan milik tepat satu pengguna.
Relasi pada diagram di atas diwujudkan dalam skema tabel pesanan:
CREATE TABLE pesanan (
id SERIAL PRIMARY KEY,
pengguna_id INTEGER NOT NULL REFERENCES pengguna(id),
total NUMERIC(12, 2) NOT NULL
);Kolom pengguna_id memakai REFERENCES pengguna(id), sehingga server menolak pesanan yang merujuk pengguna tak ada — integritas ditegakkan database, bukan kode aplikasi.
DDL, DML, dan DQL #
Perintah SQL terbagi tiga kelompok besar:
| Kelompok | Fungsi | Contoh perintah |
|---|---|---|
| DDL (Data Definition Language) | Mengubah struktur tabel | CREATE TABLE, ALTER TABLE, CREATE INDEX |
| DML (Data Manipulation Language) | Mengubah isi data | INSERT, UPDATE, DELETE |
| DQL (Data Query Language) | Membaca data | SELECT |
INSERT INTO pengguna (nama, email, umur) VALUES ('Budi', '[email protected]', 25);
SELECT nama, email FROM pengguna WHERE aktif = true;
UPDATE pengguna SET umur = 26 WHERE id = 1;Indeks: Mempercepat Pencarian #
Tanpa indeks, server memindai seluruh baris setiap kali mencari — pola yang melambat seiring pertumbuhan data. Indeks adalah struktur tambahan (umumnya B-tree) yang menjaga kecepatan pencarian berbasis kolom tertentu:
CREATE INDEX idx_pengguna_email ON pengguna(email);Primary key otomatis terindeks, sehingga pencarian berdasarkan id sudah cepat tanpa langkah tambahan.
Transaksi dan ACID #
Aplikasi sering harus mengubah beberapa tabel sebagai satu kesatuan: memindahkan saldo antar rekening tidak boleh berhenti setengah jalan. SQL menyediakan transaksi untuk itu:
BEGIN;
UPDATE rekening SET saldo = saldo - 50000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 50000 WHERE id = 2;
COMMIT;Jika salah satu UPDATE gagal, ROLLBACK membatalkan seluruh perubahan sejak BEGIN. Jaminan transaksi dirangkum dalam akronim ACID: Atomicity (semua perubahan terjadi penuh atau tidak sama sekali), Consistency (data tetap memenuhi aturan skema), Isolation (transaksi bersamaan tidak saling merusak), dan Durability (data yang di-commit bertahan meski server mati).
Normalisasi dan Bentuk Normal Pertama #
Normalisasi menata struktur tabel agar bebas duplikasi yang mengundang inkonsistensi. Aturan paling dasarnya, 1NF (First Normal Form), mensyaratkan setiap sel berisi tepat satu nilai. Program contoh.go pada bagian bawah halaman memecahkan kolom telepon multi-nilai menjadi baris terpisah — murni manipulasi data Go, tanpa menyentuh database:
func normalisasi1NF(barisan []BarisMahasiswa) []BarisTelepon {
hasil := []BarisTelepon{}
for _, b := range barisan {
for _, t := range strings.Split(b.Telepon, ",") {
hasil = append(hasil, BarisTelepon{NIM: b.NIM, Telepon: strings.TrimSpace(t)})
}
}
return hasil
}Output yang Dihasilkan:
sebelum (melanggar 1NF):
M001 Budi telepon="0812-111-2222, 0813-333-4444"
M002 Siti telepon="0815-555-6666"
sesudah (memenuhi 1NF):
M001 telepon="0812-111-2222"
M001 telepon="0813-333-4444"
M002 telepon="0815-555-6666"Mari kita telaah baris demi baris program di atas:
- Baris 10-14 (
type BarisMahasiswa): meniru baris tabel belum normal;Teleponmenyimpan banyak nilai dalam satu string. - Baris 17-20 (
type BarisTelepon): hasil normalisasi — satu nomor telepon per baris. - Baris 26 (
strings.Split): memecah isiTeleponberdasarkan koma. - Baris 27 (
hasil = append(...)): setiap potongan menjadi elemenBarisTelepon;strings.TrimSpacemembuang spasi setelah koma. - Baris 34-37 (
sebelum := ...): Budi punya dua nomor telepon — pelanggaran 1NF yang paling umum.
Dalam skema sungguhan, hasil ini berarti tabel telepon_mahasiswa terpisah dengan foreign key nim.
PostgreSQL dan Alternatif Non-Relasional #
Go dan PostgreSQL adalah pasangan umum di industri: PostgreSQL open source, standar SQL-nya kuat, dan drivernya di Go matang bernama pgx — driver yang kita pakai di seluruh section ini. Database relasional bukan satu-satunya pilihan. Cache sederhana cocok di key-value store seperti Redis; dokumen yang bentuknya sering berubah kadang lebih nyaman di document store; data sensor berjuta baris per hari sering pindah ke database time-series. Aturan praktis: mulai dari database relasional kecuali pola akses data jelas melenceng dari model tabel.
Pemahaman tabel, relasi, dan SQL di atas adalah bekal konseptual kita. Langkah berikutnya: mengenal jembatan resmi Go menuju dunia SQL, package database/sql.
Referensi #
- Tutorial: Accessing a relational database — tutorial resmi Go akses database relasional.
- pgx — PostgreSQL Driver and Toolkit — repositori resmi driver PostgreSQL untuk Go.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"fmt"
"strings"
)
// BarisMahasiswa meniru satu baris tabel mahasiswa yang belum normal:
// kolom telepon menyimpan banyak nilai dalam satu sel.
type BarisMahasiswa struct {
NIM string
Nama string
Telepon string
}
// BarisTelepon adalah hasil 1NF: satu nilai telepon per baris.
type BarisTelepon struct {
NIM string
Telepon string
}
// normalisasi1NF memecah kolom multi-nilai menjadi baris terpisah.
func normalisasi1NF(barisan []BarisMahasiswa) []BarisTelepon {
hasil := []BarisTelepon{}
for _, b := range barisan {
for _, t := range strings.Split(b.Telepon, ",") {
hasil = append(hasil, BarisTelepon{NIM: b.NIM, Telepon: strings.TrimSpace(t)})
}
}
return hasil
}
func main() {
sebelum := []BarisMahasiswa{
{NIM: "M001", Nama: "Budi", Telepon: "0812-111-2222, 0813-333-4444"},
{NIM: "M002", Nama: "Siti", Telepon: "0815-555-6666"},
}
fmt.Println("sebelum (melanggar 1NF):")
for _, b := range sebelum {
fmt.Printf(" %s %s telepon=%q\n", b.NIM, b.Nama, b.Telepon)
}
sesudah := normalisasi1NF(sebelum)
fmt.Println("sesudah (memenuhi 1NF):")
for _, b := range sesudah {
fmt.Printf(" %s telepon=%q\n", b.NIM, b.Telepon)
}
}