用 Node.js 内置的 SQLite,给小项目省掉一个数据库
以前在 Node 里用 SQLite,基本绕不开 better-sqlite3 或 sqlite3 这两个包。它们都依赖原生模块,换个 Node 版本、换台机器就可能要重新编译,在 Windows 和一些精简的 Linux 镜像上尤其麻烦。
从 Node.js 22.5 开始,官方内置了 node:sqlite 模块;22.13 / 23.4 之后不再需要加启动参数。对于个人项目、内部工具、小型网站来说,这意味着不用装任何东西就有一个可用的数据库。
基本用法
const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync('data/app.db');
db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
created_at INTEGER NOT NULL
)
`);
const insert = db.prepare('INSERT INTO notes (title, created_at) VALUES (?, ?)');
const r = insert.run('第一条笔记', Date.now());
console.log(r.lastInsertRowid);
const rows = db.prepare('SELECT * FROM notes ORDER BY id DESC LIMIT ?').all(10);
API 和 better-sqlite3 很像:prepare 得到语句对象,然后用 run / get / all 执行。
建议打开的几个选项
db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
`);
- WAL 模式:读写可以并发,写入性能也更好,几乎是必开的;
- foreign_keys:SQLite 默认不检查外键,需要手动打开;
- busy_timeout:遇到锁时等待一会儿而不是立刻报错。
事务
node:sqlite 没有提供事务的封装,自己写一个也很简单:
function tx(fn) {
db.exec('BEGIN IMMEDIATE');
try {
const result = fn();
db.exec('COMMIT');
return result;
} catch (e) {
db.exec('ROLLBACK');
throw e;
}
}
用 BEGIN IMMEDIATE 而不是默认的 BEGIN,可以在事务开始时就拿到写锁,避免"先读后写"时出现锁升级失败。
什么时候不适合
- 它是同步 API,一条慢查询会阻塞整个进程。小数据量下这不是问题,但不要在里面跑几秒钟的报表;
- 同一时刻只能有一个写入者,写入非常密集的场景不合适;
- 需要多台服务器共享同一个数据库时,应该换成 MySQL 或 PostgreSQL。
对日活几百、几千的个人网站来说,SQLite 的性能绰绰有余。真正需要注意的反而是备份——整个数据库就是一个文件,丢了就全没了。