所以,就只是一行代码的事。

import sqlite3

接着,整个的世界都陷入了安静之中。无需去安装MySQL,不必去调试Postgres,不用去央求运维人员开放端口,不用跟Docker Compose闹脾气摔键盘。

它就在那儿,一个文件,几百K,陪你从深夜写到天亮。

到底什么是SQLite

很多人以为它是“玩具”。

是。它的确是。

是那种小孩攥在手里不放的、缺了轮子但跑得飞快的玩具车。

不是特斯拉。是小时候拿电池盒和小马达自己缠线圈做的那种。

你不怕它坏,因为你知道它怎么转的。

为什么现在的AI又开始“往回”走了

你知道吗。

最近我看了一个开源项目叫OpenClaw。

它干了一件特别老土的事——用SQLite给AI当长期记忆。

不是那种被称作向量数据库的东西,不是名为Redis的事物,不是云端存在的那个所谓“持久化层”。

只有一个.sqlite文件,它安安静静地放置在~/.openclaw/memory/当中。

像个老派的日记本。

他们说这叫“Local-First”。

SQLite在Python中的应用_Python sqlite3模块示例_数据库SQLite部署

我说这叫:不想把自己的脑子交给别人托管。

谁还在用这玩意儿

我翻旧账的时候发现,Firefox的书签用它。

iOS的短信用它。

import sqlite3

你手机里点外卖的那个App,点餐记录也在用它。

甚至欧洲核子中心,对,造大型强子对撞机的那帮人。

他们在2023年的粒子物理数据表,也用SQLite存。

你以为的“玩具”,是世界运行的底噪。

conn = sqlite3.connect('database.db')

那些代码里藏着的笨拙温柔

我最喜欢的一段代码,来自那个AI记忆项目。

cursor = conn.cursor()

try:
    # 极速路径:用向量扩展直接算距离
    db.execute(vec_query)
except:
    # 安全路径:哦你装不了扩展是吧,
    # 没关系,我替你用JavaScript硬算。
    # 慢一点,但不会丢下你。

这哪是容错。

这是写代码的人在朝屏幕另一端点头:

cursor.execute('''CREATE TABLE students (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)''')

知道你不容易,我有plan B。

人们总问:它能撑多大?

官方文档说,140TB也没问题。

cursor.execute("INSERT INTO students (name, age) VALUES ('John', 20)")

但我不关心这个。

我更在意的是另一件事——

有人写了个叫absurder-sql的东西。

conn.commit()

存有SQLite于IndexedDB之中,将IndexedDB佯装成磁盘模样,使得浏览器里的WASM能够运行具备全功能的SQLite。

还要跨标签页争leader,还要导出来能在命令行打开。

荒不荒谬。极度的荒谬。

但荒谬得,像有人真的在乎数据是属于用户的。

cursor.execute("SELECT * FROM students")
rows = cursor.fetchall()
for row in rows:
    print(row)

为什么我们写不动那些“正确”的代码了

越来越多人开始在桌面应用里重新拥抱它。

不是因为它最强。

cursor.execute("SELECT * FROM students WHERE age > 18")

是因为它不欺负人

你不用为了存几个联系人就去学Kubernetes。

你不用为了做个博客就去配读写分离。

你就想写个脚本,跑起来,别出错,早点睡。

cursor.execute("UPDATE students SET age = 21 WHERE name = 'John'")

那些你可能真的搜过的问题

### SQLite到底能不能用在生产环境

能。

cursor.execute("DELETE FROM students WHERE name = 'John'")

一旦你并非属于那种,每秒能够达到几万并发的情况,数据量达到上PB的程度,没有主从复制就会觉得难以入眠的生产环境。

就多数人的那种“生产环境”而言,存在几百个用户,有一个成为管理员的人,还有一个把自己搞得累死累活的个体。

它够了。

cursor.execute("PRAGMA table_info(students)")
columns = cursor.fetchall()
for column in columns:
    print(column[1])

### 为什么我的数据库老是 locked

你开了连接忘了关。

你写了事务忘了commit。

你把简单的事情搞复杂了。

name = 'Alice'
age = 22
cursor.execute("INSERT INTO students (name, age) VALUES (?, ?)", (name, age))

### 能不能加密

原生不行。

但有SQLCipher,能AES-256。

age = 20
cursor.execute("SELECT * FROM students WHERE age > ?", (age,))

钥匙别写在代码里,钥匙放在系统钥匙串里。

### 分页很慢怎么办

别用OFFSET 10000。

记住你上次读到的那个ID。

with conn:
    conn.execute("UPDATE students SET age = 23 WHERE name = 'Alice'")

这是SQLite教你的:别一次性要太多,记住你到过哪儿。

它的墓碑

等一下,写到这里我觉得不对。

conn.rollback()

SQLite不需要被悼念。

它还活得好好的。

每年几十亿份部署。

手机、浏览器、汽车、电视机顶盒、飞机上的娱乐系统。

with open('script.sql', 'r') as file:
    sql_script = file.read()
cursor.executescript(sql_script)

它死不了。

死的是我们那种“非要用最重型武器解决最轻型问题”的执念。

尾声

我桌上有个硬盘。

last_row_id = cursor.lastrowid

里面存着十年前的博客备份,格式是.sqlite。

我打开过,还在。

所有文章、评论、留言板里陌生人的“加油”。

一行都没丢。

with sqlite3.connect('database.db') as conn:
    cursor = conn.cursor()
    # 执行数据库操作

那时候我不会写docker-compose.yml

我只会:

conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('SELECT * FROM posts')

它从来没说过“版本不兼容”。

sql_query = "INSERT INTO students (name) VALUES (?);"
data = [('Bob',), ('Carol',), ('Dave',)]
cursor.executemany(sql_query, data)

它从来没说过“依赖缺失”。

它从来没问过我:你配得上我吗。

你不觉得这才是我们最初想要的“软件”吗。

打开,能用。关上,带走。

不索求,不审判,不消失。

data = [('Bob', 21), ('Carol', 22), ('Dave', 23)]
with conn:
    conn.executemany("INSERT INTO students (name, age) VALUES (?, ?)", data)