SQLite在LLM项目中的轻量级数据存储实践:从对话到RAG的完整方案
每次做完一个LLM项目总有人问我聊天记录存哪、知识库的原文和切片放哪、还有那一堆模型参数和用户配置怎么管。我给出的答案往往都是同一句话——SQLite。对方通常一脸惊讶大模型应用不是得上PostgreSQL加专用向量数据库吗怎么又绕回这个“玩具级”数据库了这个反应我太熟悉了。其实SQLite从来不是玩具它只是被绝大多数教程忽略在了舞台边缘。但在LLM应用落地这件事上SQLite恰恰是那个最不起眼却最能打的轻量级数据存储方案。它不需要单独部署服务、不需要配账号密码、不需要操心连接池一个文件就把所有数据装进去了。这篇文章我会从存储需求拆解、表结构设计、完整代码示例到踩坑排查把SQLite怎么在LLM项目里真正扛起数据层这件事一次讲透。不管你是独立开发者、小团队的技术负责人还是刚接触大模型应用的新手这套方案都能让你少熬几个夜。1. 为什么LLM应用落地离不开轻量级数据库1.1 先拆清楚一个LLM应用到底要存什么数据很多人在设计LLM应用的数据层时第一反应是“对话记录肯定要存”然后就开始建表。但实际动手做完几个项目后你会发现需要存储的数据远比想象中复杂大致可以分成四类第一类是对话上下文和会话管理数据。LLM本身没有状态API调用完之后什么都不会记住。你要做多轮对话就必须把每次聊天的消息按会话存下来下一次请求时把历史消息重新拼进prompt里。这是最基础的需求但也是最容易被低估的。随着会话越来越长Token消耗会指数级上升所以还得对历史做截断、压缩、摘要这些中间结果也需要一个地方暂存。第二类是知识库与检索增强RAG相关数据。2024到2025年几乎所有LLM应用都绕不开RAG。你要先准备一批文档把它们切片、向量化、存起来用户提问时先在库里检索相关的片段再把检索结果塞进prompt让模型回答。这就涉及原文存储、切片内容存储、向量数据存储和来源元数据存储。每一层都有不同的读写特征和体积要求。第三类是用户配置、应用设置、Prompt模板、模型参数这类元信息。比如某个用户偏好简洁回答某个知识库用的是自定义的system prompt某个任务的temperature要调低一点。这些数据量不大但是种类杂、结构差异大还得支持运行时动态修改。第四类是运行日志和审计数据。LLM应用的每一次API调用消耗了多少Token、花了多少钱、回答了什么内容、用户最终反馈如何这些数据对成本分析和质量迭代极其重要。尤其在面向行业客户时审计需求几乎是刚需。这四类数据的特点完全不同会话数据是小写入、频繁读知识库是大对象、读多写少配置数据是低频小写入日志数据是持续追加。要在一套方案里同时满足重型数据库往往杀鸡用牛刀纯文件方案又管不住结构和一致性SQLite反而刚刚好。1.2 从选型角度聊聊为什么偏偏是SQLite我知道一定会有人问为什么不用MySQL、PostgreSQL为什么不用Faiss、Chroma、Milvus这些专门做向量的这里我把选型逻辑摊开讲。先看传统关系型数据库。你要部署MySQL或PostgreSQL意味着要维护一个独立服务进程要处理网络访问权限要管理账号密码还要设计备份恢复策略。对于一个大模型应用的前期探索和MVP阶段这些运维负担是实实在在的时间成本。而LLM项目最大的特点是迭代极快——今天用OpenAI的Embedding明天可能换成本地的BGE后天可能整个RAG链路都要重构。在这个阶段你需要的是一层足够简单、足够轻、能让你随时扔掉重构的数据存储而不是一个从一开始就绑死你的重服务。再看专用向量数据库。Faiss是一个库而不是数据库只管向量索引和检索不管原文和元数据Chroma和Milvus确实很好用但它们是独立服务而且只解决向量检索这一件事。如果你的应用里还有对话历史、配置、日志这些常规数据要存总不能让向量数据存一套、普通数据再存一套吧两套存储之间的数据同步和一致性维护够你喝一壶的。SQLite之所以成为那个“最优解”核心原因有三个。首先是零运维。它是嵌入式数据库就是一个文件加一个库不需要服务端进程不需要配置连接地址程序启动时直接打开文件就能读写。你可以把数据库文件直接丢进项目目录里跟代码一起走备份时复制文件就完事。其次是能力边界刚好够用。别小看SQLite它支持完整的SQL语法、事务、外键、视图、触发器、全文检索FTS5单表千万级数据完全能扛住。对绝大多数LLM应用的本地数据量来说性能绰绰有余。我实测过在没有做任何特殊优化的情况下SQLite执行万级记录的带索引查询都是毫秒级几千条切片的向量全表扫描也就几十毫秒这个量级完全在可用范围内。第三是生态和跨平台兼容性特别好。Python内置了sqlite3库C#有Microsoft.Data.SqliteNode.js有原生的node:sqlite模块Rust有rusqlite几乎每种主流语言都能直接打开同一个数据库文件。LLM应用往往需要多语言协作——后台用Python做数据预处理和向量化业务服务用Java或Go写数据分析脚本用Python或R跑SQLite一文件打通所有环节。表格对比一下更直观方案部署运维能力覆盖与LLM应用匹配度典型适用阶段SQLite零部署单文件消息、配置、日志、知识库、向量检索极高MVP、中小规模本地应用MySQL/PostgreSQL需独立服务不含向量检索PG有扩展但配置复杂中用户量大、团队完善的成长期专用向量数据库需独立服务只有向量能力低-中百万级向量以上的专业检索场景纯文件/JSON零部署无查询能力无事务低极简demo或一次性脚本2. 让SQLite撑起大模型应用的“五脏六腑”2.1 会话与上下文最基础也最关键的存储LLM应用的会话存储绝不是简单地把对话记录堆进一张表就完事。我做了几个项目之后才真正理解会话数据是LLM应用里读写最频繁、最容易出性能问题的部分。每次用户发一句话你至少要做一次插入存用户消息、一次查询取历史消息、再一次插入存模型回复。也就是说一次普通的对话交互数据库至少要经历三次操作。如果前端还做了流式输出那模型回复的存储频次会更高。综合下来消息表的设计有几个关键点值得好好斟酌。第一role字段必须加CHECK约束。消息的角色只能是system、user、assistant、tool几种。不加约束的话代码里一旦写错一个字符串脏数据就会悄悄混进对话里而且排查起来非常痛苦。加了约束之后数据库在写入层面就把错误挡住了。第二一定要单独保存system prompt的变化版本。我发现很多应用喜欢把system prompt跟消息混在一起存其实这是个坑。因为system prompt是会迭代的你今天用的是“你是客服助手”明天改成“你是专业的客服助手回答务必简洁”。如果不单独记版本你就无从知道某条用户消息到底是在哪个prompt版本下生成的历史回滚和对比分析的时候特别被动。第三要有意识地存Token数量。每一次调用LLM接口都会返回usage信息里面包含了提示词Token和回复Token。把这些数字存进消息表里后续想统计成本、做预算控制、分析用户的Token消耗趋势全都顺理成章。否则等账单出来再想溯源就只能一笔一笔翻日志了。很多新手在做多轮对话时喜欢把整个会话的全部历史一股脑塞给模型。这种做法在对话早期没问题但聊到一百轮之后光是拼历史就把上下文窗口塞满了。我的做法是查询历史时先按Token预算倒序取最近的消息再加一条“更早的对话内容已省略”的摘要消息作为铺垫。SQLite在这种场景下的速度表现让人放心因为它就是纯粹的本机文件访问没有网络IO的延迟。2.2 知识库与RAG向量存储也能塞进SQLite说实话我起初也怀疑过SQLite能不能扛起RAG的向量存储。毕竟是2024年之后“无向量不RAG”的时代直接说SQLite能存向量很多人第一反应就是开玩笑。但实际试下来SQLite不仅行而且对中小规模知识库来说体验相当好。RAG链路的核心流程是固定的读取文档、切片、向量化、存库用户提问时把问题向量化、相似度检索、取回上下文、拼prompt、调LLM生成答案。这里面最核心的就是切片和向量的存储检索。SQLite存向量有两个方案一个是把向量当成BLOB裸存自己实现距离计算逻辑另一个是用sqlite-vec这个扩展直接在SQL语句里算向量距离。我更推荐后者因为sqlite-vec封装好了L2距离、余弦距离等多种计算方式还支持建HNSW索引性能好了一大截。这里我特别想提一下网上流传很广的一个说法LLM的token和向量检索本质上是三个点的关系——key是“我是谁”query是“我在找什么”value是“我能提供什么”。以RAG场景为例每一个被切好的文档片段就是value它是一段“能提供给模型的上下文”这个片段的向量表示就是key它是“这段内容讲什么的数字标识”用户输入的问题向量化后就是query是“我现在想找什么”。三者在SQLite里形成了一个天然的映射关系chunks表存valuevectors索引存key检索SQL负责把query匹配到最相似的key然后把对应的value取回给LLM。这个比喻在做数据建模时特别有用。你不需要把向量存储看成一个神秘的高维空间问题它就是一张“内容-向量对照表”。而这张表用SQLite来维护再合适不过了。2.3 配置、权限与审计SQLite的隐藏价值对话和知识库是LLM应用的主航道但真正让应用能稳定运营的是那些不起眼的配置表和日志表。很多项目出问题恰恰是栽在这些“配角”身上。配置表我的做法是标准的key-value结构key设为主键value字段存JSON字符串需要时在代码里解析。有人可能会问为什么不直接存成多列因为LLM应用的配置项结构太碎片化了有的是字符串有的是数组有的是对象。用JSON可以保持结构灵活性在SQLite里读写起来也很快。日志审计表则是LLM应用特有的需求。每次模型调用API的provider、模型名、输入Token数、输出Token数、耗时、错误信息、重试次数这些都应该记录在案。一方面这是排查线上问题的核心依据另一方面在面向企业客户时模型回答的可追溯性是合同层面的要求。我做过一个面向医疗行业的LLM辅助审核项目当时客户明确要求每一次模型给出的审核结论和依据必须能够回溯到具体是哪次调用、用了什么prompt、参考了哪条知识库数据。这套追溯体系就是靠SQLite的几张日志表撑起来的成本极低效果却直接关系到项目验收。另外SQLite单文件特性在隐私保护上有天然优势。很多LLM应用涉及敏感数据不能轻易传到云端。SQLite的数据库文件可以直接放在用户本机甚至可以配合SQLCipher做整库加密。数据的物理位置完全可控这给了很多本地优先local-first的LLM应用一个非常稳妥的存储底座。3. 从零搭建SQLite LLM数据层附完整代码3.1 环境准备与数据库初始化这里我以Python环境为例因为Python是LLM生态中使用最广泛的语言。先准备好基础依赖pip install sqlite-vec openaisqlite-vec是SQLite的向量检索扩展openai是用来调用大模型接口的SDK。如果你用的是本地模型比如通过Ollama、vLLM部署的把openai换成对应的HTTP客户端就行SQLite这边的逻辑完全不受影响。初始化数据库时我建议把连接管理封装成一个工具函数避免每次调用都重复写连接参数import sqlite3 import sqlite_vec DB_PATH llm_app.db def get_connection(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row conn.execute(PRAGMA journal_modeWAL;) conn.execute(PRAGMA foreign_keysON;) sqlite_vec.load(conn) return conn这里有三行代码很关键。row_factory sqlite3.Row让查询结果能像字典一样通过字段名访问比默认的数字下标清晰太多journal_modeWAL开启SQLite的预写日志模式并发读写的体验会明显改善sqlite_vec.load(conn)把向量扩展加载进当前连接。3.2 核心表结构设计详解数据库的核心是五张表。我直接给出完整建表语句然后逐个解释字段设计意图CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, title TEXT NOT NULL DEFAULT 新会话, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, role TEXT NOT NULL CHECK (role IN (system, user, assistant, tool)), content TEXT NOT NULL, tokens INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_messages_session_time ON messages(session_id, created_at); CREATE TABLE IF NOT EXISTS documents ( id TEXT PRIMARY KEY, title TEXT NOT NULL, source TEXT, content TEXT NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS chunks ( id TEXT PRIMARY KEY, document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE, chunk_index INTEGER NOT NULL, content TEXT NOT NULL, tokens INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_chunks_document ON chunks(document_id, chunk_index); CREATE TABLE IF NOT EXISTS app_config ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL );时间字段我用的是INTEGER类型存Unix时间戳而不是TEXT类型存“2025-06-01 12:00:00”。原因很简单时间戳做范围查询、排序、比较都更高效而且不会因为格式差异导致比较结果错误。你可以在读取时用datetime.fromtimestamp转换完全不耽误使用。message表的CHECK约束我前面提过这里再强调一次不加约束的role字段就是一颗定时炸弹。设想一下代码里某处把“assistant”拼成了“assisant”模型回复就会全部静静躺在数据库里查不出来而且这个问题还不容易在测试阶段暴露。会话ID直接用UUID字符串不搞自增整数。因为外部系统比如Web前端的URL路由可能会直接引用会话ID如果暴露了自增数字等于告诉别人你的业务量。3.3 完整实现一个带记忆的LLM对话接下来是核心功能带历史记忆的对话函数。这个函数要做的事情是接收用户消息从数据库里取出最近的对话历史拼成prompt调用LLM最后把用户消息和模型回复都存回数据库。import time import uuid from datetime import datetime from openai import OpenAI client OpenAI() SYSTEM_PROMPT 你是一位乐于助人的AI助手请用简洁清晰的中文回答问题。 def create_session(title新会话): conn get_connection() session_id str(uuid.uuid4()) conn.execute( INSERT INTO sessions (id, title, created_at, updated_at) VALUES (?, ?, ?, ?), (session_id, title, int(time.time()), int(time.time())), ) conn.commit() conn.close() return session_id def get_recent_messages(session_id, max_tokens3000): conn get_connection() rows conn.execute( SELECT role, content FROM messages WHERE session_id ? ORDER BY id DESC LIMIT 20, (session_id,), ).fetchall() conn.close() messages [] total_tokens 0 for row in reversed(rows): msg_tokens len(row[content]) // 2 if total_tokens msg_tokens max_tokens: break messages.append({role: row[role], content: row[content]}) total_tokens msg_tokens return messages def chat_with_memory(session_id, user_input): conn get_connection() try: # 先保存用户消息 conn.execute( INSERT INTO messages (session_id, role, content, tokens, created_at) VALUES (?, ?, ?, ?, ?), (session_id, user, user_input, len(user_input) // 2, int(time.time())), ) conn.execute( UPDATE sessions SET updated_at ? WHERE id ?, (int(time.time()), session_id), ) conn.commit() # 组装消息列表 messages [{role: system, content: SYSTEM_PROMPT}] history get_recent_messages(session_id, max_tokens2000) messages.extend(history) # 调用LLM resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, temperature0.7, ) reply resp.choices[0].message.content.strip() # 保存助手回复注意Token统计 usage resp.usage total_tokens usage.total_tokens if usage else len(reply) // 2 conn.execute( INSERT INTO messages (session_id, role, content, tokens, created_at) VALUES (?, ?, ?, ?, ?), (session_id, assistant, reply, total_tokens, int(time.time())), ) conn.commit() return reply finally: conn.close()这段代码里有几个细节是实际项目里踩过坑才总结出来的。第一用户消息先入库再调LLM。这样设计的好处是如果LLM调用超时或者报错用户消息已经保存下来了重试时不会丢数据。我在早期版本里是先调LLM再统一入库结果有一次连环超时用户连续发了几条消息全都丢了那个教训记忆犹新。第二Token的粗略估算用“字符数除以二”。汉字的Token占用通常是1到2个英文单词平均约1.3个Token除以二是经验值。这里不要追求精确到个位关键是别把信息截断。当然模型返回的真实usage数据一定要存下来那才是精准的数据。第三get_recent_messages函数里用LIMIT 20再加Token上限双保险。如果只按Token截断有可能SQL查出来几千条消息内存直接爆掉如果只按条数限制长对话的消息数少但Token数可能超限。两个条件同时卡住才稳妥。第四写库操作放在try-finally里确保连接一定关闭。SQLite的连接是进程内文件句柄如果忘记关闭长时间运行的应用迟早把句柄耗尽报错。这种事在低并发时完全看不到量上来之后就会疯狂报“too many open files”。3.4 实现基于SQLite的RAG知识库检索对话记忆只是SQLite在LLM应用里的第一步RAG才是真正的重头戏。这里我给出一个完整的知识库文档入库和检索实现。首先是文档入库。文档进来之后要切片、向量化、写入数据库import numpy as np from openai import OpenAI client OpenAI() def embed_texts(texts): resp client.embeddings.create( modeltext-embedding-3-small, inputtexts, ) return [item.embedding for item in resp.data] def chunk_text(text, chunk_size500, overlap50): 按字符切片带重叠避免切断语义 chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) start end - overlap return chunks def add_document(doc_id, title, content, source): conn get_connection() try: # 存原文 conn.execute( INSERT INTO documents (id, title, source, content, created_at) VALUES (?, ?, ?, ?, ?), (doc_id, title, source, content, int(time.time())), ) # 切片 chunks chunk_text(content) chunk_ids [f{doc_id}_c{i} for i in range(len(chunks))] # 批量向量化 embeddings embed_texts(chunks) # 建虚拟表向量索引 conn.execute( CREATE VIRTUAL TABLE IF NOT EXISTS vec_chunks USING vec0(chunk_id TEXT PRIMARY KEY, embedding FLOAT[384]) ) # 写入切片和数据 for idx, (chunk_id, chunk, emb) in enumerate(zip(chunk_ids, chunks, embeddings)): conn.execute( INSERT INTO chunks (id, document_id, chunk_index, content) VALUES (?, ?, ?, ?), (chunk_id, doc_id, idx, chunk), ) conn.execute( INSERT INTO vec_chunks (chunk_id, embedding) VALUES (?, ?), (chunk_id, np.array(emb, dtypenp.float32).tobytes()), ) conn.commit() print(f文档已入库: {title}, 共 {len(chunks)} 个切片) finally: conn.close()这段代码里核心是sqlite-vec虚拟表的使用。FLOAT[384]的384是OpenAI text-embedding-3-small模型的向量维度如果你用的是别的模型这里的维度要跟着改。向量数据先转成numpy的float32数组再转成bytes写入BLOB字段。这里建议用参数化查询的?占位符来传二进制数据不要用字符串拼接。接着是检索函数。用户提问时把问题向量化后去虚拟表里算距离取最相似的几条def search_chunks(query, top_k5): conn get_connection() try: query_vec embed_texts([query])[0] query_bytes np.array(query_vec, dtypenp.float32).tobytes() # sqlite-vec的knn搜索 rows conn.execute( SELECT chunk_id, distance FROM vec_chunks WHERE embedding MATCH ? AND k ?, (query_bytes, top_k), ).fetchall() results [] for row in rows: chunk conn.execute( SELECT content, document_id FROM chunks WHERE id ?, (row[chunk_id],), ).fetchone() results.append({ content: chunk[content], document_id: chunk[document_id], distance: row[distance], }) return results finally: conn.close()检索出来的chunk内容就是你给LLM的RAG上下文。把它们拼接进prompt让模型参考上下文回答即可def rag_chat(session_id, user_input): results search_chunks(user_input, top_k3) context \n\n.join([f【资料{idx1}】{r[content]} for idx, r in enumerate(results)]) messages [ {role: system, content: 你是一个知识库助手请优先根据参考资料回答用户问题并标注信息来源序号。}, {role: user, content: f参考资料\n{context}\n\n用户问题{user_input}}, ] resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, ) reply resp.choices[0].message.content.strip() # 把问答和检索到的资料关联入库这里省略和对话函数一致 return reply到这里一个基于SQLite的完整RAG链路已经跑通了。整套方案没有额外启动任何服务一个数据库文件扛下了原文、切片、向量索引和业务数据。我实测在本地对一本书约20万字做切片入库大概切出500多个片段查询一次的时间在30到80毫秒之间完全不影响用户感知。4. 常见问题与排查技巧实录4.1 写库连接冲突怎么解决SQLite最著名的限制是同一时刻只有一个写连接。在LLM应用中如果后台有一个线程在批量灌知识库同时前端用户在正常聊天就有概率出现“database is locked”报错。我第一次遇到这个报错时第一反应是“SQLite不行换数据库”后来才意识到是自己没有正确配置连接模式。推荐的做法是三重保障。第一连接时开启WAL模式也就是前面代码里那句一句PRAGMA journal_modeWAL;。WAL模式允许读操作和写操作并行显著减少了锁冲突的概率。第二设置busy_timeout。当连接遇到锁时SQLite会等待而不是立刻报错。建议设置成3000毫秒conn.execute(PRAGMA busy_timeout3000;)第三保持写事务短小精悍。不要在同一个事务里既做耗时好几百毫秒的LLM API调用又做数据库写入。我在早期的知识库导入逻辑中把一个大批次的所有切片写入放在一个事务里结果事务时间过长把其他连接全部堵死了。正确做法是每批10到20条切片提交一次。如果应用确实需要更高并发可以试试WAL模式下的只读副本连接或者把写操作串行化放到独立任务队列里。绝大多数LLM应用的数据写入量并不大这两个手段基本就够用了。4.2 向量检索慢怎么办向量检索是RAG链路里最容易出现性能瓶颈的环节。纯SQLite场景数据量在几千到一两万条时全表扫描速度还能接受但如果你的知识库膨胀到几十万条切片那再全表扫描就真的跑不动了。我的经验是分三层优化。第一层是候选集粗筛。先用SQLite的LIKE或者FTS5全文索引从文本层面把范围缩小到可能相关的几百条再对这部分做向量精确计算。原理很简单如果一个切片在文本层面完全没有目标关键词那么它的向量距离大概率也不会很接近。这个方案不依赖任何扩展实现起来零成本。第二层是使用sqlite-vec自带的索引能力。较新版本的sqlite-vec已经支持HNSW等近似最近邻索引创建方式和普通虚拟表略有不同。虽然效果不如专业的向量数据库但在几十万量级以内这个性能已经够用了。第三层是设定清晰的量级边界。我个人的判断标准是数据量在30万条切片以内SQLite方案完全能打超过100万条就不要硬用SQLite了该上专业的向量数据库就上。毕竟方案有边界硬凑上去最后买单的还是自己。4.3 数据访问与迁移的实用工具箱最后分享一套跨语言访问和迁移的经验。在LLM项目里SQLite的数据库文件往往会被多个技术栈共用Python处理数据入库Java或Go提供API服务Node.js写点自动化脚本甚至可能还有C#桌面端直接读取数据。SQLite的优势在这里体现得淋漓尽致。Python端的访问方式就不多说了内置的sqlite3加sqlite-vec扩展已经足够。C#端推荐用Microsoft.Data.SqliteNuGet直接装API设计得很顺手重点是它支持SQLite的标准SQL语法你在Python里写的表结构完全可以直接复用。VB.NET也是类似逻辑用System.Data.SQLite库即可。Node.js现在更省事了自带的node:sqlite模块不需要额外安装依赖。可视化工具方面特别推荐DB4SDB Browser for SQLite开源跨平台能直接打开数据库文件看表结构、跑查询、导入导出数据。调试LLM应用的对话数据时用DB4S翻聊天记录比写SQL脚本直观得多。如果需要从MySQL迁移到SQLite注意几个坑。MySQL的AUTO_INCREMENT要改成INTEGER PRIMARY KEY AUTOINCREMENTMySQL的NOW()函数在SQLite里要用strftime(%s,now)或datetime(now)替代MySQL的ENUM类型SQLite不支持要用TEXT加CHECK约束。其实最好的办法是别手动迁移用工具导出CSV再导入省时省力。还有一个小技巧我几乎在每个项目里都用数据库文件的备份直接复制文件。SQLite支持在线备份即便应用正在运行也能通过VACUUM INTO命令生成一份一致性快照VACUUM INTO backup_20250601.db;这比任何备份工具都干净利落。LLM项目的数据库文件通常不会太大每天定时执行一次备份文件直接压缩归档成本几乎为零。在我个人做过的项目里印象最深的其实不是技术本身而是一次项目复盘时的体会。当时我们花了整整三周时间搭了一套PostgreSQL加向量数据库的架构结果上线第一周发现数据量根本没到需要分布式处理的程度而维护成本和查询复杂度却实实在在拖慢了开发节奏。后来我推倒重来用SQLite两天就把数据层重做完了性能和稳定性反而更好。大模型应用的数据存储很多时候最大的敌人不是数据量大而是方案太重。SQLite这种轻量级的存储方案帮我把更多精力留在了真正重要的模型调优和产品逻辑上。至少目前来看只要数据量还没有跨过百万级切片那条线我都会继续让SQLite做LLM应用的大后方。

相关新闻

为什么看图软件的尺寸标注是核心竞争力?从实操到兼容性全解析

为什么看图软件的尺寸标注是核心竞争力?从实操到兼容性全解析

1. 为什么看图软件反而把「尺寸标注」做成了核心竞争力1.1 常规看图工具的痛点先聊聊我自己的使用场景。过去几年,我经手的项目里有一大半的时间不是在画图,而是在"看图"——看别人画好的图、看现场报上来的图、看设计院反复修改的图。这期间试…

2026/10/1 3:49:41 阅读更多 →
C++内存越界溢出:无符号指针算术的底层真相

C++内存越界溢出:无符号指针算术的底层真相

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 3:48:40 阅读更多 →
固态硬盘坏块导致数据库无法拷贝?从故障定位到镜像修复全复盘

固态硬盘坏块导致数据库无法拷贝?从故障定位到镜像修复全复盘

前两天接了个挺典型的求助,客户说公司一台文件服务器上的普通固态硬盘最近经常卡顿,昨天直接蓝屏,重启之后系统能进,但C盘系统分区已经不太正常,D盘里放数据库文件的目录能看见,真正去拷贝MDF和LDF文件时&a…

2026/10/1 3:48:40 阅读更多 →

最新新闻

博图V18连接Factory IO:PLCSIM Advanced仿真链路与IO映射

博图V18连接Factory IO:PLCSIM Advanced仿真链路与IO映射

做自动化这行,只要你想在没硬件的情况下把一条产线逻辑跑通,就绕不开博图加仿真这套组合。这两年我身边不少做电气设计和程序调试的朋友都在琢磨同一个问题:博图 V18 和 Factory IO 到底怎么连。表面上看,这就是两个软件之间拉一根…

2026/10/1 4:59:16 阅读更多 →
C与C++的区别:从设计哲学到内存模型与工程实践全面解析

C与C++的区别:从设计哲学到内存模型与工程实践全面解析

“C和C之间到底有什么区别?”这个问题我几乎每隔几天就会被问一次。技术社区里永远有人吵,新手区里永远有人懵。你看那些搜索引擎里的热词就能知道提问者的状态:有人搜“c语言基础”和“c入门”,有人搜“vscode配置c/c环境”&…

2026/10/1 4:59:16 阅读更多 →
LLM Agent记忆系统实战:基于MCP协议与Docker部署hindsight记忆提炼方案

LLM Agent记忆系统实战:基于MCP协议与Docker部署hindsight记忆提炼方案

1. 从“hindsight”说起:为什么Agent的记忆问题值得单独拎出来做“hindsight”这个词本身很有意思,字面意思是“事后的洞察力”,也就是我们常说的“后见之明”。放在LLM Agent的语境里,它指向一个非常具体且要命的问题&#xff1a…

2026/10/1 4:59:16 阅读更多 →
Java智能物流系统实战:Spring Boot+MySQL+Drools高并发架构

Java智能物流系统实战:Spring Boot+MySQL+Drools高并发架构

简介:本资源是一份面向计算机专业本科生与Java初学者的毕业设计/课程设计参考文档,聚焦智能物流管理系统的全流程开发实践,旨在解决传统物流人工管理效率低、易出错、数据难追溯等痛点。文档以B/S架构为基底,完整呈现基于SSM&…

2026/10/1 4:59:16 阅读更多 →
AX Agent集群编排实战:Go语言下的状态机与依赖管理

AX Agent集群编排实战:Go语言下的状态机与依赖管理

1. 从 9.5K Star 的 AX 说起:Agent 集群编排到底在解决什么问题第一次看到 AX 这个项目的时候,我正被一堆散落在不同机器上的 Agent 进程搞得焦头烂额。每个 Agent 单独跑都没问题,但一旦需要它们协同完成一个稍复杂的任务链,问题…

2026/10/1 4:59:16 阅读更多 →
车队综合业务管理系统:从需求分析到技术落地的完整实践

车队综合业务管理系统:从需求分析到技术落地的完整实践

做过车队综合业务管理系统之后,再回头看这个选题,最大的感受是:它不是靠某一个惊艳算法撑起来的,而是靠“把散落在Excel、微信群和口头电话里的信息,整齐地收进一套系统里”这件事本身。车队综合业务管理系统的核心价值…

2026/10/1 4:58:15 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →