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

| | Python
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

jika masih prototype atau tool internal, maka SQLite lebih dari cukup:

AspekSQLiteMySQL
Servertidak perlu, satu file .dbbutuh server di port 3306
Installbawaan Python import sqlite3butuh mysql-connector / PyMySQL + server
Concurrent tulissatu tulis dalam satu waktubanyak tulis barengan
Cocok untukprototype, todo app, cache lokal, testingAPI, e-commerce, multi user
Batasfile bisa sampai TB, tapi tulis serialscale 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 KEY di SQLite itu alias untuk rowid, otomatis jadi auto increment walau tanpa tulisan AUTOINCREMENT. bedanya, AUTOINCREMENT mencegah reuse id yang sudah dihapus, tapi sedikit lebih lambat. untuk kebanyakan kasus, INTEGER PRIMARY KEY saja 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_TIMESTAMP langsung isi waktu sekarang tanpa perlu datetime.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 error Incorrect 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, sqlite3 sudah dukung conn.execute() langsung tanpa bikin cursor dulu. jadi conn.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=10 biar nunggu 10 detik sebelum nyerah locked
  • PRAGMA journal_mode=WAL biar read tidak block write
  • with untuk tiap transaksi, jangan tahan koneksi lama-lama
  • CREATE INDEX di kolom yang sering di WHERE atau ORDER 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 TABLE di 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, ?, dan WAL, tidak perlu refactor besar untuk 2026. cukup tahu fitur STRICT dan RETURNING biar 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 conn biar otomatis commit/rollback dan close, ini cara paling aman di 2026
  • row_factory = sqlite3.Row bikin hasil bisa diakses pakai row["title"], dan executemany untuk insert massal
  • aktifkan PRAGMA journal_mode=WAL dan foreign_keys=ON, plus timeout=10 biar jarang database 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 pakai sqlite3.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.

Sigit Nurhanafi avatar
Full-stack developer & technical writer. Berpengalaman di PHP, Laravel, NodeJS, MySQL, dan Python. Aktif menulis tutorial pemrograman dan maintaining open-source projects di PemburuKode sejak 2021.