SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor · Dikemas kini 2026-08-07

DuckDB vs SQLite: Mana Satu Pilihan Terbaik?

Ketahui perbezaan utama antara SQLite untuk transaksi data dan DuckDB untuk analisis fail Parquet atau CSV. Artikel ini menjelaskan mengapa VPS anda perlukan kedua-duanya.

DuckDB berbanding SQLite pada pelayan: jawapan satu ayat

SQLite ialah enjin OLTP (online transaction processing): ia menyimpan data dalam bentuk baris dan dibina untuk membaca serta menulis beberapa baris pada satu masa, dengan selamat dan pantas. DuckDB ialah enjin OLAP (online analytical processing): ia menyimpan data dalam bentuk lajur dan dibina untuk mengimbas jutaan baris serta memberikan satu nilai agregat. Kedua-duanya merupakan pustaka terbenam (embedded libraries), kedua-duanya membuka fail biasa, dan tiada satu pun yang menjalankan proses pelayan yang perlu anda pantau.

Oleh itu, jawapan jujur bagi soalan "yang mana satu" hampir selalunya "kedua-duanya, pada VPS yang sama". Aplikasi anda menyimpan status langsung (live state) dalam SQLite. Laporan anda membaca fail Parquet dan CSV dengan DuckDB. Kedua-duanya tidak bersaing kerana ia tidak melakukan tugas yang sama.

Mengapa storan baris dan storan lajur mengubah jawapannya

SQLite menulis satu baris sebagai satu bahagian yang bersambung dalam sesuatu halaman. Mengambil satu pesanan menggunakan kunci primarinya akan menyentuh satu halaman indeks dan satu halaman data, iaitu dua bacaan. Inilah perkara yang dilakukan oleh aplikasi beribu-ribu kali sesaat: membaca pengguna ini, mengemas kini sesi ini, memasukkan pesanan ini.

DuckDB menulis setiap lajur secara berasingan dan memampatkannya. Menjumlahkan amount_cents merentasi lima juta baris hanya membaca lajur amount_cents, melangkau setiap bait lain dalam fail, dan menjalankan hasil tambah melalui kod tervektor ke atas kelompok nilai. Lajur-lajur lain tidak pernah dibaca daripada cakera, dan di sinilah kelajuannya datang.

Sekarang, jalankan setiap enjin menggunakan beban kerja enjin yang satu lagi. SQLite yang menjumlahkan satu lajur perlu melayari setiap baris dan menarik keseluruhan baris daripada halaman untuk mencapai satu medan, jadi ia membaca lebih banyak data daripada cakera berbanding yang diperlukan. DuckDB yang memasukkan satu pesanan perlu menyentuh storan setiap lajur untuk satu nilai sahaja, dan ia mengambil kunci tulis pada keseluruhan fail pangkalan data untuk melakukannya. Tiada enjin yang rosak. Setiap satunya sedang menjawab soalan yang tidak direka untuknya.

Kelebihan SQLite: status aplikasi transaksional

Pilih SQLite apabila penulisan data bersaiz kecil, kerap berlaku, dan tidak boleh hilang. Ini termasuk sesi, pesanan, baris giliran, tetapan, atau apa-apa sahaja yang dicipta oleh permintaan web.

sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/app

Cipta jadual dan aktifkan write-ahead logging dalam langkah yang sama.

sqlite3 /srv/app/app.db <<'SQL'
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS orders (
  id           INTEGER PRIMARY KEY,
  customer     TEXT    NOT NULL,
  placed_at    TEXT    NOT NULL,
  amount_cents INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS orders_customer ON orders (customer);
INSERT INTO orders (customer, placed_at, amount_cents)
  VALUES ('ana', '2026-07-30T09:14:00Z', 4200);
SQL

Baris pertama output ialah wal. Ini adalah PRAGMA yang melaporkan mod yang telah ditukar, dan ia merupakan tetapan paling berguna pada pelayan. Dalam mod rollback journal lalai, penulis akan menyekat setiap pembaca. Dalam mod WAL, pembaca terus membaca status terakhir yang telah disahkan (committed) sementara seorang penulis menambah data, jadi laporan yang perlahan tidak lagi melambatkan permintaan web di belakangnya.

Semak sama ada baris tersebut telah kembali:

sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"

Anda akan mendapat 1|ana|2026-07-30T09:14:00Z|4200. Dua lagi fail muncul bersebelahan pangkalan data, iaitu app.db-wal dan app.db-shm, dan kedua-duanya adalah milik pangkalan data tersebut. Menyalin app.db sahaja semasa aplikasi sedang berjalan akan menghasilkan sandaran yang rosak (torn backup), yang akan dibincangkan dengan lebih lanjut di bawah.

SQLite masih membenarkan seorang penulis pada satu-satu masa. Had tersebut adalah kunci, bukan giliran, jadi penulis kedua yang menunggu terlalu lama akan gagal dengan database is locked dan bukannya menyekat selama-lamanya. Tingkatkan masa menunggu dengan PRAGMA busy_timeout = 5000; pada setiap sambungan yang dibuka oleh aplikasi anda. Kesabaran selama lima saat akan menghapuskan kebanyakan ralat ini dalam beban kerja web biasa.

Kelebihan DuckDB: analitik ke atas fail sedia ada

Pilih DuckDB apabila soalan bermula dengan "berapa banyak", "berapa jumlah" atau "yang mana sepuluh teratas", dan inputnya terdiri daripada himpunan fail CSV atau Parquet. Pasang klien baris perintah, versi 1.5.5 setakat Julai 2026:

curl https://install.duckdb.org | sh

Skrip ini memasang binari di bawah ~/.duckdb/cli/latest/duckdb dan mencetak baris yang menambahkannya ke dalam PATH anda. Sahkan ia berjalan:

~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"

Bina fail yang realistik untuk dibuat kuiri. Ini menulis lima juta baris pesanan ke dalam Parquet, dimampatkan dengan zstd:

mkdir -p /srv/data
duckdb :memory: "
COPY (
  SELECT i                                                     AS id,
         'cust_' || (i % 5000)                                 AS customer,
         TIMESTAMP '2026-01-01 00:00:00' + INTERVAL (i) MINUTE AS placed_at,
         (i * 37) % 20000                                      AS amount_cents
  FROM range(5000000) t(i)
) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);"

Sekarang, ajukan soalan analitik tersebut. Buka shell, hidupkan pemasa, dan buat kuiri pada fail tersebut secara terus tanpa langkah import:

.timer on
SELECT customer,
       count(*)                AS orders,
       sum(amount_cents)/100.0 AS revenue
FROM '/srv/data/orders.parquet'
GROUP BY customer
ORDER BY revenue DESC
LIMIT 5;

Baca nombor anda sendiri daripada .timer dan bukannya mempercayai nombor yang diterbitkan, kerana hasilnya bergantung pada cakera dan bilangan teras (core) anda. Bentuknya adalah perkara yang penting. Tiada CREATE TABLE, tiada INSERT dan tiada langkah pemuatan: DuckDB membaca footer Parquet, menentukan ketulan lajur (column chunks) yang diperlukan oleh kuiri, dan hanya membaca bahagian tersebut. Keseluruhan direktori berfungsi dengan cara yang sama menggunakan glob, FROM '/srv/data/orders-*.parquet', yang merupakan cara eksport harian selama sebulan menjadi satu kuiri.

Kelajuan cakera adalah asas kepada semua ini, dan imbasan lajur merupakan bacaan berjujukan yang panjang, jadi jurang antara NVMe dan storan SATA lama pada VPS kelihatan lebih jelas di sini berbanding di bawah bacaan rawak kecil SQLite.

Membaca pangkalan data SQLite anda daripada DuckDB

Kedua-dua enjin ini berhubung melalui sambungan sqlite DuckDB. Lampirkan pangkalan data aplikasi dalam mod baca sahaja, supaya pertanyaan analitik tidak akan mengubah keadaan data secara langsung:

INSTALL sqlite;
LOAD sqlite;
ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY);
SELECT customer, count(*) AS orders
FROM app.orders
GROUP BY customer
ORDER BY orders DESC;

Ini membaca baris daripada fail SQLite semasa pertanyaan dijalankan tanpa melakukan penyalinan. Kaedah ini mudah tetapi tidak pantas, kerana data pada cakera masih dalam format storan baris dan DuckDB perlu melaluinya. Gunakan kaedah ini untuk eksport, bukan untuk papan pemuka yang dimuat semula setiap tiga puluh saat:

COPY (SELECT * FROM app.orders)
  TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);

Satu pernyataan itu adalah keseluruhan corak yang digunakan. SQLite menyimpan baris data terkini yang aktif. Eksport berjadual menukarkan tempoh data yang telah ditutup kepada format Parquet. DuckDB menjawab setiap soalan yang merangkumi tempoh berbulan-bulan, manakala pangkalan data aplikasi kekal kecil, yang memastikan operasi tulisnya sentiasa pantas.

Jalankan eksport mengikut jadual dan bukannya secara manual. Pasangan servis dan pemasa systemd adalah saiz yang tepat untuk tugas ini: satu unit yang menjalankan COPY, dan satu pemasa yang mencetuskannya setiap malam.

Menjalankan kedua-duanya pada satu VPS

Tiada apa-apa di sini yang memerlukan container dan tiada apa-apa yang memerlukan port. Kedua-dua enjin adalah pustaka, jadi pemasangannya hanyalah pakej dan laluan fail. Jika timbunan perisian anda yang lain sudah berjalan di bawah Docker Compose pada VPS yang sama, lekapkan direktori data ke dalam container yang memerlukannya dan bukannya menambah servis pangkalan data, kerana tiada servis untuk ditambah.

Dua peraturan memastikan susunan ini tidak bermasalah.

Berikan setiap enjin direktorinya sendiri: /srv/app untuk fail SQLite yang ditulis oleh aplikasi, /srv/data untuk fail Parquet yang dibaca oleh analitik. Apabila ia berkongsi direktori, kerja sandaran yang mengambil snapshot satu proses akan bertindih dengan proses yang lain.

Jangan halakan dua proses ke satu fail pangkalan data DuckDB dalam mod baca-tulis. Hanya satu proses boleh memegang fail DuckDB untuk penulisan, dan proses kedua akan gagal membukanya sama sekali. Banyak pembaca adalah dibenarkan apabila setiap satunya menetapkan access_mode = 'READ_ONLY'. Ini mengejutkan pengguna yang datang daripada SQLite, di mana beberapa proses berkongsi fail secara rutin. Jika analitik anda hanya membaca fail Parquet, persoalan ini tidak akan timbul, yang merupakan satu lagi sebab untuk menyimpan keadaan tahan lama (durable state) dalam SQLite.

Sandaran adalah berbeza, dan perbezaan itu membawa masalah

Pangkalan data SQLite yang sedang berjalan terdiri daripada tiga fail, dan menyalinnya dengan cp semasa proses penulisan sedang berlaku akan menghasilkan fail yang boleh dibuka tetapi mengandungi data yang salah. Gunakan arahan sandaran enjin itu sendiri, yang mengambil syot kilat (snapshot) yang konsisten sementara aplikasi terus menulis:

sqlite3 /srv/app/app.db ".backup '/srv/backup/app-$(date -u +%Y%m%dT%H%M%SZ).db'"
sqlite3 /srv/backup/app-20260730T091400Z.db "PRAGMA integrity_check;"

integrity_check akan mencetak ok jika salinan tersebut adalah baik. Sebarang output lain bermakna anda perlu membuang syot kilat tersebut dan mengambil yang baharu.

Fail Parquet tidak pernah berubah selepas ia ditulis, jadi ia tidak memerlukan pengendalian khas: sandarkan direktori tersebut. Hantar kedua-dua laluan keluar dari pelayan dengan sandaran restic daripada VPS anda, dan keseluruhan lapisan data tersebut akan menjadi dua direktori dalam satu tugasan sandaran.

Mod kegagalan dan rentetan tepat yang akan anda lihat

Error: database is locked daripada SQLite bermaksud sambungan lain memegang kunci tulis lebih lama daripada masa tamat (timeout) yang dibenarkan. Ini bukan kerosakan data. Tetapkan PRAGMA busy_timeout pada setiap sambungan, kemudian cari transaksi panjang yang sepatutnya dipecahkan kepada beberapa transaksi pendek.

Error: unable to open database file selepas perubahan kebenaran (permission) biasanya bermaksud proses tersebut boleh menulis fail tetapi tidak pada direktori fail itu. SQLite mencipta app.db-wal dan app.db-shm bersebelahan dengan pangkalan data, jadi direktori itu sendiri mestilah boleh ditulis, bukan sekadar fail .db sahaja.

IO Error: Could not set lock on file daripada DuckDB bermaksud proses kedua sudah membuka pangkalan data tersebut untuk penulisan. Tutup shell yang satu lagi, atau buka shell anda dalam mod baca sahaja (read only).

Out of Memory Error daripada DuckDB pada VPS kecil bermaksud pertanyaan (query) memerlukan lebih banyak memori kerja daripada yang tersedia. DuckDB akan melakukan spill ke cakera apabila mampu, jadi sediakan ruang untuk spill dengan membuka fail pangkalan data pada cakera dan bukannya :memory:, serta hadkan penggunaannya dengan SET memory_limit = '2GB';. Pada mesin yang menjalankan servis lain, had tersebut adalah perkara yang menghalang pertanyaan ad hoc daripada menolak aplikasi anda keluar daripada RAM.

Binder Error: Referenced column "amount" not found semasa membuat pertanyaan pada Parquet hampir selalu bermaksud skema fail tersebut bukan seperti yang anda ingat. Jalankan DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; dan baca semula nama lajur yang sebenar.

Cara memilih dalam praktikal

Tanya apakah corak penulisan yang digunakan. Banyak penulisan kecil yang mesti terselamat daripada gangguan bekalan elektrik bermakna SQLite. Tanya apakah corak bacaan yang digunakan. Imbasan penuh dengan agregat ke atas sejarah yang panjang bermakna DuckDB. Kebanyakan sistem sebenar menjawab ya kepada kedua-dua soalan, dan respons yang tepat adalah dengan memberikan setiap enjin bahagian yang ia cekap, bukannya memaksa salah satu daripadanya untuk menampung yang lain.

Migrasi yang perlu dielakkan adalah memindahkan status aplikasi yang sedang berjalan ke dalam DuckDB hanya kerana sesuatu laporan menjadi perlahan. Laporan tersebut perlahan disebabkan oleh susun atur storan, jadi penyelesaiannya adalah dengan melakukan eksport, bukannya menulis semula laluan penulisan anda.

FAQ

Bolehkah DuckDB menggantikan SQLite untuk pangkalan data aplikasi saya?

Tidak, jika aplikasi anda kerap melakukan penulisan. DuckDB mengambil kunci tulis ke atas keseluruhan fail pangkalan data, membenarkan hanya satu proses baca-tulis pada satu masa, dan ditala untuk perubahan pukal dan bukannya sisipan baris tunggal. Kekalkan status transaksi dalam SQLite dan biarkan DuckDB membacanya dengan ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); apabila laporan memerlukannya.

Adakah DuckDB benar-benar lebih pantas daripada SQLite untuk analitik?

Untuk imbasan dan agregat ke atas jadual yang besar, ya, dan sebabnya ialah susun atur storan dan bukannya helah penalaan. DuckDB hanya membaca lajur yang dinamakan oleh pertanyaan dan memproses nilai dalam kelompok, manakala SQLite perlu melayari keseluruhan baris untuk mencapai satu medan. Untuk mendapatkan satu baris tunggal melalui kunci utama, keadaannya terbalik, kerana SQLite menyentuh dua halaman manakala DuckDB menyentuh storan setiap lajur.

Adakah saya memerlukan banyak RAM untuk menjalankan DuckDB pada VPS?

Tidak, tetapi tetapkan had dan gunakan cakera. Buka fail pangkalan data dan bukannya :memory: supaya DuckDB boleh menumpahkan hasil perantaraan ke cakera, kemudian tetapkan SET memory_limit = '2GB'; kepada nilai yang boleh ditampung oleh VPS anda. Tanpa had, satu GROUP BY yang besar boleh meningkatkan Out of Memory Error atau menolak servis lain keluar daripada RAM.

Bagaimanakah cara saya memasukkan data SQLite saya ke dalam Parquet?

Lampirkan fail SQLite daripada DuckDB dan salin pertanyaan terus keluar dengan COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);. Jalankannya mengikut jadual untuk tempoh yang telah ditutup, seperti baris bulan lepas, dan biarkan baris terkini dalam SQLite di mana aplikasi masih menulis kepadanya.

Yang mana satu perlu saya sandarkan, dan bagaimana?

Kedua-duanya, dengan cara yang berbeza. Ambil syot kilas (snapshot) SQLite dengan sqlite3 app.db ".backup '/srv/backup/app.db'" dan bukannya cp, kerana pangkalan data yang sedang berjalan juga merupakan fail -wal dan -shm, dan salinan biasa boleh menjadi rosak. Fail Parquet tidak pernah berubah setelah ditulis, jadi menyalin direktori sudah memadai.