MySQL adalah database yang paling banyak dipasangkan dengan PHP — kombinasi keduanya bahkan punya nama tersendiri: tumpukan LAMP (Linux, Apache, MySQL, PHP). Berbeda dengan SQLite yang menyatu di dalam proses PHP, MySQL berjalan sebagai server terpisah: aplikasi Anda membuka koneksi jaringan, mengirim query, dan menerima hasilnya. Pada chapter ini kita akan membahas bentuk koneksi dari PHP ke MySQL, alasan charset utf8mb4 wajib disebut di DSN, pilihan antara PDO dan ekstensi mysqli, serta perbedaan dialek SQL yang perlu Anda sesuaikan saat memindahkan skema dari SQLite ke MySQL. Karena lingkungan latihan kita tidak menjalankan server MySQL, contoh koneksi nyata ditampilkan sebagai blok khusus tanpa klaim output, sementara logika yang bisa diuji tetap dieksekusi sungguhan.

Driver MySQL pada PHP #

PHP menyediakan dua cara utama mengakses MySQL: driver pdo_mysql untuk PDO, dan ekstensi mysqli (MySQL Improved) dengan API-nya sendiri. Ketersediaan keduanya bergantung pada kompilasi PHP, sehingga pemeriksaan pertama sebelum menulis kode adalah extension_loaded():

php
<?php
var_dump(extension_loaded('pdo_mysql'), extension_loaded('mysqli'));

echo "driver PDO tersedia: " . implode(', ', PDO::getAvailableDrivers()) . "\n";

Output yang Dihasilkan:

text
bool(true)
bool(false)
driver PDO tersedia: mysql, pgsql, sqlite

Pada build PHP yang kita pakai, pdo_mysql aktif (bool(true)) sedangkan mysqli tidak dikompilasi (bool(false)) — dan hal seperti ini normal terjadi di dunia nyata, karena distribusi PHP sering memangkas ekstensi yang tidak dipakai. Daftar driver pada baris terakhir menunjukkan bahwa PDO siap bicara dengan mysql, pgsql, dan sqlite sekaligus. Di bawah kedua API tersebut, pengiriman data ke server ditangani pustaka mysqlnd (MySQL Native Driver) yang dikompilasi bawaan PHP modern.

Membangun Koneksi dengan DSN #

Koneksi PDO ke MySQL dibuat lewat DSN berformat mysql:host=...;port=...;dbname=...;charset=..., diikuti nama pengguna dan kata sandi sebagai argumen kedua dan ketiga. Bentuk lengkapnya seperti ini:

php
<?php
$pdo = new PDO(
    'mysql:host=127.0.0.1;port=3306;dbname=toko;charset=utf8mb4',
    'budi',
    'YOUR_DATABASE_PASSWORD',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ],
);

Mari kita bedah setiap bagiannya: host adalah alamat server MySQL (127.0.0.1 berarti mesin yang sama), port boleh dihilangkan bila memakai port bawaan 3306, dbname memilih database awal untuk koneksi, dan charset menetapkan encoding seluruh percakapan koneksi. Username dan password dikirim sebagai argumen terpisah — bukan bagian DSN — dan array opsi keempat menyetel error mode exception beserta fetch mode bawaan, sama seperti kebiasaan kita di SQLite. Setelah koneksi terbuka, seluruh teknik dari chapter-chapter sebelumnya berlaku tanpa perubahan: prepare(), parameter binding, transactions, sampai pola error handling.

Pada aplikasi nyata, detail koneksi tidak ditulis literal di kode, melainkan dibaca dari environment variable atau berkas konfigurasi. Fungsi kecil berikut merakit DSN dari array konfigurasi sehingga nilai default (port 3306, charset utf8mb4) terpusat di satu tempat:

php
<?php
function buatDsnMysql(array $konfigurasi): string
{
    $bagian = [
        'host' => $konfigurasi['host'],
        'port' => $konfigurasi['port'] ?? 3306,
        'dbname' => $konfigurasi['dbname'],
        'charset' => $konfigurasi['charset'] ?? 'utf8mb4',
    ];

    $pasangan = [];
    foreach ($bagian as $kunci => $nilai) {
        $pasangan[] = "$kunci=$nilai";
    }

    return 'mysql:' . implode(';', $pasangan);
}

echo buatDsnMysql(['host' => '127.0.0.1', 'dbname' => 'toko']) . "\n";
echo buatDsnMysql(['host' => 'db.internal', 'port' => 3307, 'dbname' => 'toko_prod']) . "\n";

Output yang Dihasilkan:

text
mysql:host=127.0.0.1;port=3306;dbname=toko;charset=utf8mb4
mysql:host=db.internal;port=3307;dbname=toko_prod;charset=utf8mb4

Kedua baris hasil menunjukkan bagaimana nilai bawaan mengisi bagian yang tidak ditentukan: konfigurasi pertama otomatis mendapat port=3306, dan keduanya selalu diakhiri charset=utf8mb4. Dengan pola seperti ini, berpindah antara lingkungan pengembangan dan produksi hanya berarti mengganti isi array konfigurasi — kode SQL dan kode PHP tidak tersentuh.

Charset utf8mb4, Bukan utf8 #

Bagian charset=utf8mb4 pada DSN sering dianggap pelengkap, padahal fungsinya krusial. Charset utf8 pada MySQL hanyalah alias bagi utf8mb3 — UTF-8 yang dipotong maksimal tiga byte per karakter. Karakter empat byte seperti emoji (contohnya 🎉 yang butuh empat byte) dan sebagian aksara CJK gagal tersimpan, memicu galat Incorrect string value atau data yang terpotong diam-diam. Charset utf8mb4 adalah UTF-8 yang utuh dan menjadi bawaan MySQL sejak versi 8.0.

Menyebut charset di DSN juga menyetel encoding pada arah dua tempat sekaligus: string yang dikirim PHP ke server dan hasil yang dikirim server ke PHP. Karena pengaturan ini terjadi saat jabat tangan koneksi, mysqlnd menanganinya tanpa query tambahan — lebih bersih daripada menjalankan SET NAMES utf8mb4 manual setelah koneksi dibuka.

mysqli atau PDO #

Ekstensi mysqli dan PDO sama-sama mature, tapi keduanya memilih filosofi API yang berbeda. Tabel berikut merangkum pertimbangan yang paling berpengaruh:

AspekPDO + pdo_mysqlmysqli
Dukungan databaseSQLite, MySQL, PostgreSQL, dan lainnyahanya MySQL/MariaDB
Penanda bernama (:harga)yatidak, hanya ?
Gaya APIobject dan sedikit proceduralobject dan procedural lengkap
Fetch ke classPDO::FETCH_CLASSget_result() + fetch_object()
Multi-query dan fitur spesifik MySQLterbataslengkap

Untuk kode baru, PDO adalah rekomendasi standar komunitas PHP: satu API yang sama di semua database berarti keahlian Anda portabel, dan seluruh pola pertahanan SQL injection dari chapter sebelumnya bekerja identik. Ekstensi mysqli tetap relevan untuk kode lama atau ketika fitur khas MySQL seperti pemanggilan multi-statement dalam satu panggilan benar-benar dibutuhkan; mengganti API di tengah proyek besar jarang sepadan manfaatnya.

Migrasi Skema dari SQLite ke MySQL #

Pindah dari SQLite ke MySQL tidak sekadar memindahkan berkas — skema SQL harus diterjemahkan ke dialek MySQL. Tabel berikut memetakan perbedaan yang paling sering Anda temui:

AspekSQLiteMySQL
ID bertambah otomatisINTEGER PRIMARY KEY AUTOINCREMENTINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
String panjang variabelTEXTVARCHAR(n) dengan batas eksplisit
Default waktuDEFAULT (datetime('now'))DEFAULT CURRENT_TIMESTAMP
Penggabungan string'a' || 'b'CONCAT('a', 'b')
Kutip identifier"kolom"`kolom` (backtick)
Insert banyak barisper statement cukup lazimVALUES (...), (...) multi-baris efisien

Sebagai contoh konkret, mari kita ambil skema pesanan versi SQLite dan jalankan seperti biasa di :memory::

php
<?php
$skema = <<<'SQL'
CREATE TABLE pesanan (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    pelanggan TEXT NOT NULL,
    total INTEGER NOT NULL DEFAULT 0,
    dibuat_pada TEXT DEFAULT (datetime('now'))
);
SQL;

$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec($skema);

$pdo->exec("INSERT INTO pesanan (pelanggan, total) VALUES ('Budi', 50000)");
$row = $pdo->query("SELECT id, pelanggan, total FROM pesanan")->fetch(PDO::FETCH_ASSOC);
echo "#{$row['id']} {$row['pelanggan']} Rp{$row['total']}\n";

Output yang Dihasilkan:

text
#1 Budi Rp50000

Padanan tabel yang sama dalam dialek MySQL tampak seperti berikut — perhatikan AUTO_INCREMENT yang wajib menempel pada kolom kunci, VARCHAR yang menuntut panjang, dan CURRENT_TIMESTAMP pengganti datetime('now'):

sql
CREATE TABLE pesanan (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    pelanggan VARCHAR(120) NOT NULL,
    total INT UNSIGNED NOT NULL DEFAULT 0,
    dibuat_pada DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE = InnoDB;

Di sisi PHP, hampir tidak ada yang berubah: prepared statements, parameter binding, dan fetching bekerja identik. Dua penyesuaian kecil yang patut dicatat: PDO::lastInsertId() tetap mengembalikan ID baru dari AUTO_INCREMENT sehingga pola insert-then-read tetap mulus, dan PDO::PARAM_INT menjadi lebih penting karena MySQL lebih ketat soal tipe dibanding SQLite — kabar yang sudah kita antisipasi sejak chapter Parameter Binding .

MySQL Lokal dengan Docker #

Cara termudah mendapatkan server MySQL untuk latihan adalah menjalankannya dalam container Docker. Satu perintah berikut menyiapkan server MySQL 8 dengan database awal toko:

bash
docker run --name mysql-latihan \
  -e MYSQL_ROOT_PASSWORD=YOUR_DATABASE_PASSWORD \
  -e MYSQL_DATABASE=toko \
  -p 3306:3306 \
  -d mysql:8

Environment variable MYSQL_ROOT_PASSWORD mengatur kata sandi akun root, MYSQL_DATABASE otomatis membuat database bernama toko saat container pertama kali menyala, dan -p 3306:3306 meneruskan port container ke mesin Anda. Setelah container siap — proses inisialisasi pertama memakan waktu beberapa detik — DSN dari contoh awal chapter ini langsung tersambung: mysql:host=127.0.0.1;dbname=toko;charset=utf8mb4. Untuk menghentikan sementara gunakan docker stop mysql-latihan, dan docker rm mysql-latihan menghapus container beserta datanya.

Ringkasan #

MySQL menggeser database Anda keluar dari proses PHP menjadi server terpisah yang diakses lewat DSN mysql:host;dbname;charset. Charset utf8mb4 wajib eksplisit agar karakter empat byte tersimpan utuh, PDO lebih disarankan daripada mysqli untuk kode baru karena portabilitasnya, dan migrasi skema dari SQLite berarti menerjemahkan AUTOINCREMENT menjadi AUTO_INCREMENT, TEXT menjadi VARCHAR(n), serta menyesuaikan fungsi waktu dan penggabungan string. Docker membuat server latihan tersedia dalam satu perintah. Selanjutnya kita berkenalan dengan database server yang menjadi favorit aplikasi skala berat — PostgreSQL — dan melihat fitur-fitur dialeknya yang membuat PHP developer kerap memilihnya.

Referensi #