Belajar Python Database SQLite: CRUD Lengkap dengan sqlite3 untuk Pemula 2026

SQLite adalah database yang tersimpan di satu file. Python memiliki modul sqlite3 bawaan, tanpa pip install apa pun.
jika kalian baru belajar database di Python, maka SQLite adalah tempat mulai yang paling ramah. SQLite adalah pilihan pertama saya untuk aplikasi kecil dan prototype, tidak perlu server, tidak perlu konfigurasi, cukup satu file data.db langsung jalan. banyak aplikasi yang saya buat mulai dari SQLite lalu pindah ke MySQL saat sudah butuh banyak user, dan polanya selalu sama 🙂. panduan ini saya tulis dari alur itu, biar kalian bisa langsung pakai untuk project nyata.
kalau kalian sudah baca file handling di Python, maka konsep open dan with akan terasa mirip di sini. bedanya, yang dibuka sekarang bukan .txt tapi .db.
Daftar Isi
- 1. Kapan pakai SQLite dan kapan pindah MySQL
- 2. Koneksi dan membuat tabel
- 3. CRUD lengkap dengan class
- 4. Parameterized query biar aman
- 5. With statement dan transaksi
- 6. Row factory dan executemany
- 7. WAL mode dan tips anti locked
- 8. Studi kasus: aplikasi catatan sederhana
- 9. Backup dan pindah ke MySQL
- 10. Update September 2026
1. Kapan Pakai SQLite dan Kapan Pindah MySQL
jika masih prototype atau tool internal, maka SQLite lebih dari cukup:
| Aspek | SQLite | MySQL |
|---|---|---|
| Server | tidak perlu, satu file .db | butuh server di port 3306 |
| Install | bawaan Python import sqlite3 | butuh mysql-connector / PyMySQL + server |
| Concurrent tulis | satu tulis dalam satu waktu | banyak tulis barengan |
| Cocok untuk | prototype, todo app, cache lokal, testing | API, e-commerce, multi user |
| Batas | file bisa sampai TB, tapi tulis serial | scale horizontal lebih mudah |
aturan praktis saya: kalau masih satu orang yang nulis dan baca, pakai SQLite dulu. begitu sudah butuh login banyak user barengan atau deploy ke VPS, pindah ke cara koneksi Python ke MySQL. keduanya pakai SQL yang hampir sama, jadi migrasi tidak terlalu sakit.
2. Koneksi dan Membuat Tabel
jika sudah siap, maka koneksinya cuma satu baris:
import sqlite3
conn = sqlite3.connect("data.db")
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
print("tabel siap, sqlite versi:", sqlite3.sqlite_version)
conn.close()
sqlite3.connect("data.db") akan bikin file kalau belum ada, kalau sudah ada tinggal dibuka. IF NOT EXISTS itu penting, biar script bisa dijalanin berulang tanpa error table already exists.
sedikit catatan 2026 yang sering bikin bingung:
INTEGER PRIMARY KEYdi SQLite itu alias untukrowid, otomatis jadi auto increment walau tanpa tulisanAUTOINCREMENT. bedanya,AUTOINCREMENTmencegah reuse id yang sudah dihapus, tapi sedikit lebih lambat. untuk kebanyakan kasus,INTEGER PRIMARY KEYsaja sudah cukup.- tipe di SQLite itu fleksibel (
TEXT,INTEGER,REAL,BLOB,NULL), tapi tetap tulis tipe yang jelas biar enak migrasi ke MySQL nanti. TIMESTAMP DEFAULT CURRENT_TIMESTAMPlangsung isi waktu sekarang tanpa perludatetime.now()dari Python.
cek tabel sudah jadi:
with sqlite3.connect("data.db") as conn:
cur = conn.cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
print(cur.fetchall())
3. CRUD Lengkap dengan Class
jika koneksi sudah oke, maka langkah selanjutnya adalah bikin class kecil biar CRUD rapi, mirip pola yang nanti kepakai di MySQL:
import sqlite3
from pathlib import Path
class NotesDB:
def __init__(self, db_path="data.db"):
# timeout=10 biar tidak langsung database is locked
self.conn = sqlite3.connect(db_path, timeout=10, check_same_thread=False)
# biar hasil bisa diakses pakai nama kolom
self.conn.row_factory = sqlite3.Row
self.c = self.conn.cursor()
self._buat_tabel()
def _buat_tabel(self):
self.c.execute("""
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
self.conn.commit()
def create(self, title, content):
self.c.execute("INSERT INTO notes (title, content) VALUES (?, ?)",
(title, content))
self.conn.commit()
return self.c.lastrowid
def read_all(self):
self.c.execute("SELECT * FROM notes ORDER BY created_at DESC")
return self.c.fetchall()
def read_one(self, id):
self.c.execute("SELECT * FROM notes WHERE id = ?", (id,))
return self.c.fetchone()
def update(self, id, title, content):
self.c.execute("UPDATE notes SET title=?, content=? WHERE id=?",
(title, content, id))
self.conn.commit()
return self.c.rowcount
def delete(self, id):
self.c.execute("DELETE FROM notes WHERE id=?", (id,))
self.conn.commit()
return self.c.rowcount
def close(self):
self.conn.close()
# demo
if __name__ == "__main__":
db = NotesDB()
note_id = db.create("Belajar Python", "Hari ini belajar SQLite")
print(f"ID baru: {note_id}")
for row in db.read_all():
# karena row_factory = sqlite3.Row, bisa pakai row["title"]
print(f"{row['id']}. {row['title']} - {row['content']}")
db.update(note_id, "Belajar SQLite", "Sudah paham CRUD")
print("after update:", dict(db.read_one(note_id)))
db.delete(note_id)
print("sisa:", len(db.read_all()))
db.close()
jika kalian perhatikan, maka row_factory = sqlite3.Row bikin fetch balikin Row yang bisa diakses pakai row["title"] atau row[1], jauh lebih enak daripada hafal index.
untuk Python 3.12 ke atas, sqlite3 sudah support autocommit lewat isolation_level=None kalau mau, tapi untuk pemula biarkan default saja dan panggil commit() eksplisit.
4. Parameterized Query Biar Aman
jika ada input dari user, maka jangan pernah gabung string pakai f-string:
# ❌ JANGAN - rentan SQL Injection
user_input = "'; DROP TABLE notes; --"
c.execute(f"SELECT * FROM notes WHERE title = '{user_input}'")
# ✅ PAKAI INI - aman
c.execute("SELECT * FROM notes WHERE title = ?", (user_input,))
# untuk LIKE, tetap pakai ?
keyword = "python"
c.execute("SELECT * FROM notes WHERE title LIKE ?", (f"%{keyword}%",))
# batch insert pakai executemany, jangan loop execute
data = [("Catatan 1", "isi 1"), ("Catatan 2", "isi 2")]
c.executemany("INSERT INTO notes (title, content) VALUES (?, ?)", data)
conn.commit()
beda dengan MySQL yang pakai %s, di SQLite placeholder-nya ? untuk qmark style, atau :nama untuk named style:
# named style, enak kalau kolom banyak
c.execute("INSERT INTO notes (title, content) VALUES (:title, :content)",
{"title": "Judul", "content": "Isi"})
pengalaman saya: bug paling sering di SQLite bukan query-nya, tapi lupa koma di tuple satu elemen.
(id,)harus pakai koma, kalau(id)itu bukan tuple dan bakal errorIncorrect number of bindings.
5. With Statement dan Transaksi
jika kalian sudah nyaman dengan with open() di file handling, maka with sqlite3.connect polanya sama persis:
import sqlite3
# with otomatis commit kalau sukses, rollback kalau error
with sqlite3.connect("data.db") as conn:
cur = conn.cursor()
cur.execute("INSERT INTO notes (title, content) VALUES (?, ?)", ("A", "isi A"))
cur.execute("INSERT INTO notes (title, content) VALUES (?, ?)", ("B", "isi B"))
# tidak perlu conn.commit(), keluar with langsung commit
# transaksi manual kalau butuh kontrol
conn = sqlite3.connect("data.db")
try:
cur = conn.cursor()
cur.execute("UPDATE notes SET content=? WHERE id=?", ("baru", 1))
cur.execute("INSERT INTO notes (title) VALUES (?)", (None,)) # sengaja error NOT NULL
conn.commit()
except sqlite3.IntegrityError as e:
print("gagal, rollback:", e)
conn.rollback()
finally:
conn.close()
di 2026, with adalah cara yang saya sarankan untuk semua koneksi SQLite, soalnya dia nutup koneksi dan handle commit/rollback otomatis. mirip try-finally yang dibahas di error handling, tapi lebih pendek.
6. Row Factory dan executemany
jika butuh hasil yang rapi, maka row_factory dan executemany adalah dua hal yang wajib kenal:
import sqlite3
with sqlite3.connect("data.db") as conn:
conn.row_factory = sqlite3.Row
cur = conn.cursor()
# fetch sebagai dict
cur.execute("SELECT * FROM notes WHERE id = ?", (1,))
row = cur.fetchone()
if row:
print(dict(row))
# ambil semua sebagai list dict
cur.execute("SELECT * FROM notes")
semua = [dict(r) for r in cur.fetchall()]
print(semua)
# executemany untuk 1000 baris, jauh lebih cepat daripada loop
banyak = [(f"judul {i}", f"isi {i}") for i in range(1000)]
cur.executemany("INSERT INTO notes (title, content) VALUES (?, ?)", banyak)
# executescript untuk jalankan banyak statement sekaligus (misal init schema)
cur.executescript("""
CREATE INDEX IF NOT EXISTS idx_title ON notes(title);
CREATE INDEX IF NOT EXISTS idx_created ON notes(created_at);
""")
# commit otomatis
# cek versi sqlite
print(sqlite3.sqlite_version)
print(sqlite3.version) # versi modul python
tip 2026: di Python 3.12,
sqlite3sudah dukungconn.execute()langsung tanpa bikin cursor dulu. jadiconn.execute("SELECT ...")juga jalan, lebih pendek untuk query sekali jalan.
7. WAL Mode dan Tips Anti Locked
jika aplikasi kalian mulai database is locked, maka ini bukan bug, tapi sifat SQLite yang tulisnya serial:
import sqlite3
with sqlite3.connect("data.db", timeout=10) as conn:
# WAL = Write-Ahead Logging, baca dan tulis bisa barengan, jauh lebih jarang locked
conn.execute("PRAGMA journal_mode=WAL;")
conn.execute("PRAGMA synchronous=NORMAL;")
conn.execute("PRAGMA foreign_keys=ON;") # aktifkan FK, default OFF di SQLite
print(conn.execute("PRAGMA journal_mode;").fetchone())
kombinasi yang saya pakai di 2026 untuk project kecil:
timeout=10biar nunggu 10 detik sebelum nyerahlockedPRAGMA journal_mode=WALbiar read tidak block writewithuntuk tiap transaksi, jangan tahan koneksi lama-lamaCREATE INDEXdi kolom yang sering diWHEREatauORDER BY
ingat, foreign_keys di SQLite default mati. kalau kalian bikin FOREIGN KEY tapi tidak PRAGMA foreign_keys=ON, maka FK tidak akan dicek sama sekali, mirip jebakan MyISAM di MySQL.
untuk aplikasi web dengan banyak user nulis barengan, tetap saya sarankan pindah ke MySQL atau pakai aiosqlite kalau mau async:
pip install aiosqlite
import aiosqlite
import asyncio
async def main():
async with aiosqlite.connect("data.db") as db:
await db.execute("INSERT INTO notes (title, content) VALUES (?, ?)", ("async", "hello"))
await db.commit()
async with db.execute("SELECT * FROM notes") as cur:
rows = await cur.fetchall()
print(rows)
asyncio.run(main())
8. Studi Kasus: Aplikasi Catatan Sederhana
jika semua di atas digabung, maka jadinya aplikasi catatan yang bisa langsung dipakai, mirip studi kasus di file handling tapi sekarang nyimpen di database:
import sqlite3
from datetime import datetime
from pathlib import Path
DB = Path("catatan.db")
def init_db():
with sqlite3.connect(DB) as conn:
conn.execute("PRAGMA journal_mode=WAL;")
conn.execute("""
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_title ON notes(title)")
def tambah(title, content):
with sqlite3.connect(DB) as conn:
cur = conn.execute("INSERT INTO notes (title, content) VALUES (?, ?)", (title, content))
print(f"tersimpan id {cur.lastrowid}")
def cari(keyword):
with sqlite3.connect(DB) as conn:
conn.row_factory = sqlite3.Row
cur = conn.execute("SELECT * FROM notes WHERE title LIKE ? ORDER BY created_at DESC", (f"%{keyword}%",))
for row in cur.fetchall():
print(f"{row['id']}. {row['title']} | {row['content'][:30]}")
def hapus(id):
with sqlite3.connect(DB) as conn:
cur = conn.execute("DELETE FROM notes WHERE id = ?", (id,))
if cur.rowcount:
print(f"hapus id {id} sukses")
else:
print("id tidak ditemukan")
# pakai
init_db()
tambah("Belajar SQLite", "hari ini paham with dan WAL")
tambah("Ide konten", "tulis artikel perbandingan SQLite vs MySQL")
cari("SQLite")
# hapus(1)
jika dijalankan, maka file catatan.db akan muncul di folder project, bisa di-copy, di-backup, atau dikirim ke teman, dan dia tetap satu file. enak buat dibagikan.
9. Backup dan Pindah ke MySQL
jika database sudah berisi data penting, maka backup itu wajib sebelum eksperimen:
import sqlite3
# backup ke file baru
with sqlite3.connect("data.db") as src:
with sqlite3.connect("backup.db") as dst:
src.backup(dst)
print("backup selesai")
# dump ke SQL text (buat pindah ke MySQL)
with sqlite3.connect("data.db") as conn:
dump = "\n".join(conn.iterdump())
Path("dump.sql").write_text(dump, encoding="utf-8")
print("dump tersimpan, baris:", len(dump.splitlines()))
iterdump() hasilkan file .sql yang bisa dibaca manusia, cocok buat lihat isi atau migrasi awal. tapi kalau mau migrasi serius ke MySQL, cek panduan cara koneksi Python ke MySQL karena tipe AUTOINCREMENT dan TEXT di SQLite perlu disesuaikan jadi BIGINT AUTO_INCREMENT dan VARCHAR di MySQL.
jangan khawatir kalau dump SQLite tidak 100% kompatibel dengan MySQL, itu wajar. yang penting data dan struktur utamanya kebawa, sisanya tinggal sesuaikan
CREATE TABLEdi MySQL.
10. Update September 2026: Apa yang Baru di SQLite dan Python
jika kalian update Python di September 2026, maka yang kepasang adalah Python 3.13 dan SQLite 3.46+, dan ini yang perlu kalian tahu:
Python 3.13 bawa sqlite3 lebih ketat. autocommit sekarang lebih jelas dengan isolation_level=None untuk autocommit beneran, dan default tetap transaksi. kalau kalian lihat tutorial lama yang tulis conn.isolation_level = None tanpa penjelasan, sekarang dokumentasi sudah sarankan pakai with saja untuk commit otomatis, jangan ubah isolation_level kecuali paham.
SQLite STRICT table makin dipakai. di 2026 banyak template baru pakai:
CREATE TABLE notes_strict(
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
content TEXT
) STRICT;
STRICT bikin SQLite cek tipe beneran, tidak lagi fleksibel masukin string ke kolom INTEGER. enak buat yang nanti mau migrasi ke MySQL, karena tipe lebih konsisten.
RETURNING dan UPSERT sudah stabil. kalau mau insert dan langsung dapat id tanpa lastrowid, atau update kalau sudah ada:
with sqlite3.connect("data.db") as conn:
cur = conn.execute("INSERT INTO notes(title) VALUES (?) RETURNING id", ("baru",))
print(cur.fetchone()[0])
conn.execute("""
INSERT INTO notes(id, title) VALUES (?, ?)
ON CONFLICT(id) DO UPDATE SET title=excluded.title
""", (1, "update"))
aiosqlite untuk async. untuk FastAPI atau scheduler async, aiosqlite tetap pilihan utama di 2026, API-nya mirip sqlite3 tapi pakai await. kalau project kalian masih sync, tidak perlu pindah.
uv ganti pip di banyak tutorial baru. uv pip install aiosqlite jauh lebih cepat dan bikin uv.lock, tapi pip install tetap jalan. untuk SQLite sendiri tidak perlu driver tambahan, jadi uv cuma kepakai kalau tambah aiosqlite atau pysqlite.
ringkasnya, kalau kode kalian sudah pakai
with,?, danWAL, tidak perlu refactor besar untuk 2026. cukup tahu fiturSTRICTdanRETURNINGbiar kode lebih siap migrasi ke MySQL nanti.
Kesimpulan
sqlite3.connect()buka file.db,cursor.execute()jalankan SQL,?untuk parameterized query,commit()simpan,fetchone()/fetchall()ambil hasil- pakai
with sqlite3.connect(...) as connbiar otomatiscommit/rollbackdan close, ini cara paling aman di 2026 row_factory = sqlite3.Rowbikin hasil bisa diakses pakairow["title"], danexecutemanyuntuk insert massal- aktifkan
PRAGMA journal_mode=WALdanforeign_keys=ON, plustimeout=10biar jarangdatabase is locked - SQLite cocok untuk prototype dan app kecil, kalau sudah butuh banyak tulis barengan, pindah ke MySQL dengan Python
- untuk async di Python 3.12+, pakai
aiosqlite, dan untuk cek versi pakaisqlite3.sqlite_version
habis ini kalian bisa lanjut ke pip dan virtual environment kalau install masih bingung, atau langsung ke MySQL buat naik kelas, dan jangan lupa penanganan error yang rapi di try except.
Baca Juga Mengenai :
Pertanyaan yang Sering Diajukan
Apa itu SQLite di Python?
SQLite adalah database ringan satu file yang sudah bawaan Python lewat modul sqlite3. tidak perlu install server, cukup import sqlite3 dan langsung pakai, cocok untuk prototype dan aplikasi kecil.
Apa beda SQLite dengan MySQL?
SQLite satu file, tanpa server, cepat untuk single user. MySQL butuh server, lebih kuat untuk banyak user barengan. kalau SQLite sudah mulai lambat atau butuh akses concurrent tinggi, saatnya pindah ke MySQL.
Kenapa harus pakai parameterized query dengan tanda tanya?
karena tanda tanya di sqlite3 mencegah SQL Injection. kalau pakai f-string, input user bisa menyuntik query. pakai ? dan kirim tuple, driver yang akan escape otomatis.
Apakah perlu commit setiap habis execute?
ya, untuk INSERT, UPDATE, DELETE wajib commit biar kesimpan. kalau pakai with sqlite3.connect sebagai context manager, commit otomatis saat keluar block tanpa error, rollback otomatis kalau error.
Bagaimana cara mencegah database locked di SQLite?
pakai timeout di connect, aktifkan WAL mode dengan PRAGMA journal_mode=WAL, dan jangan buka dua koneksi tulis barengan tanpa with. untuk aplikasi web, pertimbangkan pindah ke MySQL atau pakai aiosqlite.
Apakah SQLite cocok untuk production?
cocok untuk aplikasi kecil, tool internal, atau mobile app. kalau sudah butuh banyak tulis barengan, user ribuan, atau fitur user management, MySQL atau PostgreSQL lebih aman.
Bagaimana update SQLite ke versi terbaru di Python 2026?
di Python 3.12 ke atas, sqlite3 sudah ikut versi SQLite terbaru. cek dengan import sqlite3; print(sqlite3.sqlite_version). kalau butuh async, pakai library aiosqlite.

