Berkas Basis Data

Anatomi satu berkas basis data SQLite dan cara menyalinnya dengan aman.

Daftar isi

Satu basis data SQLite berarti satu berkas. Definisi tabel, baris data, indeks, sampai pengaturan internalnya tersimpan dalam satu berkas tunggal yang dapat dipindahkan seperti berkas biasa. Topik ini membedah kenyataan tersebut, mulai dari tanda format di awal berkas, pembagian isi ke dalam halaman, hubungan ukuran berkas dengan jumlah halamannya, sampai aturan menyalin basis data dengan aman.

Satu berkas untuk satu basis data#

Basis data contoh perpustakaan dibuat dengan perintah berikut, kini lengkap dengan tabel peminjaman.

bash
# membuat basis data perpustakaan.db beserta tabel dan datanya
sqlite3 perpustakaan.db <<'SQL'
CREATE TABLE buku (
  id      INTEGER PRIMARY KEY,
  judul   TEXT NOT NULL,
  penulis TEXT NOT NULL,
  tahun   INTEGER
);
CREATE TABLE anggota (
  id     INTEGER PRIMARY KEY,
  nama   TEXT NOT NULL,
  gabung DATE
);
CREATE TABLE peminjaman (
  id         INTEGER PRIMARY KEY,
  buku_id    INTEGER NOT NULL REFERENCES buku(id),
  anggota_id INTEGER NOT NULL REFERENCES anggota(id),
  pinjam     DATE NOT NULL,
  kembali    DATE
);
INSERT INTO buku (judul, penulis, tahun) VALUES
  ('Kisah Kota Tua', 'Sari Wulandari', 2018),
  ('Anak Sungai', 'Rahmat Hidayat', 2021),
  ('Musim di Ujung Jalan', 'Lina Marlina', 2019);
INSERT INTO anggota (nama, gabung) VALUES
  ('Ali', '2026-01-12'),
  ('Budi', '2026-02-03');
INSERT INTO peminjaman (buku_id, anggota_id, pinjam, kembali) VALUES
  (1, 1, '2026-02-10', '2026-02-17'),
  (2, 2, '2026-02-14', NULL),
  (3, 1, '2026-03-01', NULL);
SQL

Pemeriksaan sederhana menunjukkan wujudnya. Pola *.db pada perintah ls hanya menemukan satu berkas.

bash
# seluruh basis data berada dalam satu berkas tanpa pendamping
ls *.db
text Keluaran
perpustakaan.db

Seluruh isi basis data, dari CREATE TABLE sampai baris terakhir, berada di dalam perpustakaan.db itu. Mesin basis data server biasanya memakai banyak berkas yang dikelola sebuah proses server, sedangkan SQLite menyimpan semuanya dalam satu berkas yang dibaca langsung oleh programnya. Berkas pendamping seperti jurnal hanya muncul sesaat selama transaksi berjalan dan hilang kembali setelahnya.

Kesatuan ini menjadi dasar banyak kebiasaan pada SQLite. Memindahkan basis data cukup dengan memindahkan satu berkas, mengarsipkannya pun cukup satu berkas yang sama, dan perintah penyalinan pada bagian akhir topik ini bekerja pada berkas tersebut. Nama dan ekstensinya bebas dipilih karena sqlite3 hanya mengikuti nama yang diberikan, meskipun akhiran .db atau .sqlite3 lazim dipakai agar mudah dikenali. Tiga tabel contoh di atas pun tinggal satu berkas, sekalipun isi basis datanya terus bertambah.

Tanda format pada 16 byte pertama#

Setiap berkas basis data SQLite diawali 16 byte yang tetap. Lima belas byte pertama berupa teks SQLite format 3, dan byte ke-16 berupa karakter nol penutupnya. Perintah head menyalin 16 byte pertama, kemudian od menampilkannya per byte agar karakter nol ikut terlihat.

bash
# 16 byte pertama perpustakaan.db diperlihatkan per byte
head -c 16 perpustakaan.db | od -c
text Keluaran
0000000   S   Q   L   i   t   e       f   o   r   m   a   t       3  \0
0000020

Penanda tetap ini disebut juga nomor ajaib (magic number), yaitu rangkaian byte pembuka yang mengidentifikasi jenis berkas. Karena posisinya selalu di awal, program dapat mengenali berkas SQLite dari isinya tanpa bergantung pada ekstensi .db. Berkas dengan nama apa pun tetap terbaca oleh sqlite3 selama 16 byte pertamanya sama, dan berkas rusak pun dapat dicurigai dari penanda ini yang berubah.

Tanda inilah yang diperiksa sqlite3 setiap kali berkas basis data dibuka. Apabila 16 byte pertama tidak sesuai, misalnya karena berkas terpotong atau tertukar dengan berkas lain, sqlite3 menolak membukanya dengan pesan error file is not a database. Penolakan dini semacam ini lebih aman daripada membaca isi acak lalu menyajikan data yang tidak valid seolah-olah semuanya baik-baik saja.

Halaman dan ukuran berkas#

SQLite tidak menulis data di posisi sembarang. Berkas basis data dibagi ke dalam unit tetap yang disebut halaman (page), dan ukuran halaman bawaannya 4096 byte. Pernyataan PRAGMA membaca atau mengatur parameter internal SQLite, dan dua perintah berikut menampilkan ukuran halaman serta jumlah halaman yang terpakai.

bash
# ukuran halaman dan jumlah halaman basis data
sqlite3 perpustakaan.db "PRAGMA page_size;"
sqlite3 perpustakaan.db "PRAGMA page_count;"
text Keluaran
4096
4

Ukuran berkas menurut sistem berkas dapat dilihat dengan mengekstrak kolom kelima dari ls -l, yaitu kolom ukuran dalam byte.

bash
# ukuran berkas dilihat dari tingkat sistem berkas
ls -l perpustakaan.db | awk '{print $5}'
text Keluaran
16384

Angka 16384 sama persis dengan 4096 dikali 4, yaitu ukuran halaman dikali jumlah halamannya. Kecocokan ini bukan kebetulan, karena berkas basis data selalu tersusun dari halaman utuh sehingga ukurannya selalu kelipatan ukuran halaman. Pemahaman ini membantu menaksir pertumbuhan berkas ketika data bertambah, sebab penambahan data baru mendorong berkas naik satu halaman penuh, bukan bertambah sebagian byte.

Ukuran halaman ini ditetapkan ketika berkas basis data pertama kali dibuat, dan pernyataan PRAGMA page_size hanya berpengaruh pada basis data kosong atau setelah VACUUM dijalankan. Selain page_count, terdapat pula PRAGMA freelist_count yang menghitung halaman kosong tersisa dari baris yang pernah dihapus. Selisih kedua nilai itulah yang menunjukkan seberapa banyak ruang yang dapat dipadatkan oleh VACUUM INTO pada bagian berikutnya.

Menyalin basis data dengan aman#

Menyalin berkas basis data dengan cp memang tampak praktis, tetapi cara itu berisiko ketika basis data sedang dipakai menulis. Salinan yang diambil di tengah transaksi dapat berada dalam keadaan setengah jadi dan gagal dibuka. sqlite3 menyediakan perintah titik .backup yang menyalin isi basis data secara konsisten ke berkas baru.

bash
# .backup menyalin isi basis data ke berkas baru secara konsisten
printf '.backup salinan.db\n' | sqlite3 perpustakaan.db
# salinan diperiksa isinya
sqlite3 -header -column salinan.db "SELECT count(*) AS jumlah_buku FROM buku;"
text Keluaran
jumlah_buku
-----------
          3

Cara kedua adalah pernyataan VACUUM INTO, yang menulis ulang seluruh isi basis data ke berkas baru dalam keadaan terkompaksi tanpa halaman kosong.

bash
# VACUUM INTO menulis salinan baru yang terkompaksi
sqlite3 perpustakaan.db "VACUUM INTO 'arsip.db'"
# arsip juga dapat dibuka seperti basis data biasa
sqlite3 arsip.db "PRAGMA page_count;"
text Keluaran
4

Jumlah halaman arsip sama dengan asalnya karena basis data contoh belum meninggalkan halaman kosong. Pada basis data yang pernah menghapus banyak baris, hasil VACUUM INTO justru lebih ramping karena halaman yang terbuang ikut dipangkas. Kini direktori kerja memuat tiga berkas basis data.

bash
# direktori kerja kini memuat tiga berkas basis data
ls *.db
text Keluaran
arsip.db
perpustakaan.db
salinan.db

Salinan hasil kedua perintah itu berdiri sendiri dan langsung dapat dibuka. Pembahasan mendalam atas kedua perintah tersebut menyusul pada modul operasional bersama transaksi dan jurnal WAL. Untuk saat ini, aturan sederhananya sudah cukup, yaitu memakai perintah bawaan SQLite untuk menyalin basis data yang sedang hidup. Salinan biasa disisakan hanya untuk berkas yang tidak sedang dipakai.

Ringkasan#

  • Satu basis data SQLite adalah satu berkas yang diawali 16 byte penanda SQLite format 3.
  • Isi berkas terbagi ke halaman 4096 byte, dan page_count dikali page_size menghasilkan ukuran berkasnya.
  • .backup dan VACUUM INTO menghasilkan salinan konsisten, sedangkan cp berisiko ketika basis data sedang ditulis.

Referensi#