欢迎光临
我们一直在努力

SQLite 顶不住了?本地轻量服务该怎么选数据库

最近手头几个本地轻量服务(SQLite 打底)在并发小写入的场景下都开始喘。但这玩意是本地轻量服务,不能塞一堆重依赖,PG/MySQL 那一套直接 pass。借着给两个具体场景做选型的机会,我把市面上还能用的本地轻量方案摸了一遍,把结论和判断过程记下来,也算给你做决策时省点查资料的时间。

两个场景长这样:

  • 一个局域网 CMDB,监控 1 万多个 IoT 嵌入式服务的端口心跳,大约每 30 秒一跳,存 7 天用来展示。
  • 局域网里几十路摄像头,一直在录 30 秒一段的短视频,攒了 30~90 天的量要处理。

先说结论,后面再展开为什么。

先说结论

  • 瓶颈大概率不是 SQLite 本身,是写法。 1 万设备 × 30 秒 = 平均才 333 次写入/秒,对批量写入的 SQLite 来说根本不算压力。绝大多数卡死都是因为每条心跳独立提交 + synchronous=FULL(每次提交都 fsync 到磁盘)+ 没开 WAL,再加上并发写互相抢锁。先开 WAL、单写协程攒批,往往零成本就能解决,不用动架构。
  • 「单二进制」和「重依赖」是两回事。 VictoriaMetrics、QuestDB、MinIO、NATS 都只是一个静态可执行文件,往机器上丢一个 20~60MB 的 binary 就能跑,没有 initdb、没有服务账号、没有后台守护进程。这跟装 PostgreSQL/MySQL 完全不是一回事。所以第一步先想清楚:你能不能接受「多跑一个进程」。
  • 场景一是时序负载,最对路的是时序库。 CMDB 心跳本质是时间序列 + 高频小写入 + 时间范围查询,VictoriaMetrics 单节点(vmsingle)最合适,前面接 NATS JetStream 做缓冲能彻底把生产者和存储解耦。
  • 场景二是 Blob 问题,不是数据库问题。 30 秒一段的视频,几十路攒 90 天可能是 5~15 TB,这东西绝不能进库。用 MinIO(S3 对象存储)或者干脆哈希目录存文件系统,数据库只存元数据(路径、摄像头、时长、处理状态)。这种元数据写入速率极低,SQLite 管这个绰绰有余。
  • 一句话:场景一 SQLite WAL 调优兜底、上量上 VictoriaMetrics(可选 NATS 缓冲);场景二 MinIO 存视频 + SQLite 存元数据。 全程不碰 PG/MySQL,轻量约束守住。

    先别急着换库,看看瓶颈到底在哪

    动手加任何组件之前,先确认瓶颈的性质。我见过太多「SQLite 并发瓶颈」其实都是下面这些能改的写法,不是 SQLite 的硬上限:

    反模式实际发生了什么怎么改
    每条心跳独立 autocommit 每次提交都触发一次 fsync,磁盘 IOPS 成了天花板,几十 TPS 就卡死 攒批:单写线程 + 队列,每 1000 条一个事务
    journal_mode=DELETE + synchronous=FULL 回滚日志 + 每事务刷盘,写写互斥,并发时疯狂 SQLITE_BUSY 改 WAL + synchronous=NORMAL
    多线程共用一个连接 SQLite 连接不是线程安全的,串行化 + 锁竞争 每线程/协程独立连接,或单写者模型
    没设 busy_timeout 一冲突就报错而不是重试,要么丢数据要么重试风暴 PRAGMA busy_timeout=5000
    万级设备全在 :00/:30 打点 瞬时万级写入尖峰,单连接扛不住 设备端加随机 jitter(±几秒)+ 服务端批量缓冲

    说个我自己踩过的坑:SQLite 在 WAL 模式 + 批量事务下,单写者持续 5 万~10 万次插入/秒是能跑到的。你的场景平均才 333 次/秒,即便最坏情况下「万级尖峰」也只需在秒级内消化掉——这完全落在 SQLite 的能力圈里。所以我个人的建议是,换库前务必先做这一轮调优,大概率零成本解决。

    调优清单就这几个 PRAGMA,连接建立后执行一次:

    PRAGMA journal_mode=WAL; — 写前日志:读不阻塞写,可以并发读
    PRAGMA synchronous=NORMAL; — WAL 下只需 WAL 文件 fsync,不必每事务刷盘
    PRAGMA busy_timeout=5000; — 遇锁等 5s 重试,而不是立刻 SQLITE_BUSY
    PRAGMA wal_autocheckpoint=2000; — 控制 checkpoint 频率,降低抖动
    PRAGMA temp_store=MEMORY;

    — 写入侧:单写协程 + 批量事务(伪代码)
    BEGIN;
    INSERT INTO heartbeat(device_id,ts,port,status,latency) VALUES (?,?,?,?,?); — × 1000,一次 COMMIT
    COMMIT;

    两个场景的负载画像(先算清楚账)

    选型之前得先知道自己在扛多大的量,不然容易被厂商宣传带跑。

    场景一:局域网 IoT 心跳监控(CMDB)

    指标值
    被监控的 IoT 服务数 10,000+
    平均心跳写入速率 ~333/s
    7 天滚动窗口内行数 ~2.0 亿
    列式压缩后存储 2~4 GB(原始约 16GB)

    算法:1 万服务 ×(每 30s 一次)= 333 次/秒;单日 1 万 × 2880(每天 30s 间隔数)= 2880 万行;7 天 ≈ 2.0 亿行。每行(设备ID + 时间戳 + 端口 + 状态 + 延迟)约 80 字节 → 原始约 16GB,时序库列存压缩后一般压到 2~4GB。

    查询特征大致三类:① 每个服务「最新一次心跳」状态(last-value);② 时间范围趋势图(需要降采样);③ 7 天自动过期(retention)。

    场景二:局域网摄像头短视频

    指标值
    单段视频时长 30s
    单摄像头 / 天 段数 2,880
    20 路 × 30 天 的片段数 ~5.2M
    视频体量 5~15 TB(看路数和天数)

    这俩场景本质不一样,别混为一谈。场景一是「高频小写入 + 时间查询」,走时序库或批量 SQLite;场景二是「海量大文件 + 低频元数据」,走对象存储 + 轻量元数据库。把 30 秒视频塞进任何 SQL/NoSQL 的「值」里,结果都是数据库膨胀、备份灾难、查询变慢。视频进对象存储,数据库只管元数据,这是铁律。

    产品全景

    按「需不需要独立进程」分成两条主线。先泼盆冷水:几乎所有「嵌入式」方案本质仍是单写者,真要多个进程同时高频写,必须走单二进制服务或者更重的分布式层(而分布式层写也是串行的,只为高可用)。

    A. 先优化 SQLite(零迁移)

    • SQLite(WAL 调优) —— 单写者 + WAL 并发读;单文件 .db,零依赖;场景一调优后够用,场景二元数据首选。先做这步,我估计 80% 的情况不用换库。

    B. 嵌入式库(进程内)

    • LMDB —— MVCC:读永不阻塞写、写永不阻塞读;单写者,mmap 直读,读性能极强(OpenLDAP 同款)。适合读多写少、last-value 点查。Key-Value,没 SQL。
    • RocksDB —— LSM-Tree,为写入优化,单写者但写吞吐极高(Kafka/TiKV 底层同款思路)。适合进程内写洪流,但 C++ 依赖偏重、没 SQL。
    • bbolt(Go)/ sled(Rust) —— 单写 + 多读;如果你的服务本身是 Go / Rust 栈,直接引库最顺手。
    • DuckDB —— 单进程内可读写并发(无冲突的写,尤其 append,可以并行);多进程写入不支持。列式引擎,分析查询、降采样、报表极佳,不适合做高频并发写入的主存储。

    C. SQLite 衍生 / 复制层

    • libSQL —— SQLite 的 fork,继承了单写者模型(官方明确:并发写入是 Turso 另一套 Rust 重写的数据库才解决)。新增了嵌入式副本、HTTP Server 远程访问。它不解决写并发瓶颈,只有当你需要「SQLite + 边缘副本 / 远程访问」时才值得换。别被「SQLite fork」这五个字骗去治并发。
    • rqlite / dqlite —— 通过 Raft 做复制与高可用;写入仍要经过 leader 串行化——提升的是可用性 / 读扩展,不是写吞吐。需要多机 HA/容灾时才上。
    • Litestream —— 后台进程把 SQLite 变更增量复制到文件 / S3,是容灾备份,不是并发方案。

    D. 单二进制服务(sidecar)

    • VictoriaMetrics(vmsingle) —— 场景一首选。为时序写入高度优化,单节点就能吃下百万级 samples/s;列存压缩,7 天 retention 自动过期。一个静态二进制 ./victoria-metrics-prod,无外部依赖;兼容 InfluxDB / Prometheus / OpenTSDB / JSON 行协议。
    • QuestDB / InfluxDB v3 —— 同为单二进制时序库;QuestDB 用 SQL + InfluxDB 行协议、ingestion 极快;InfluxDB 生态成熟。场景一备选。
    • MinIO —— 场景二首选。自托管 S3 兼容对象存储,一个二进制起服务;为海量文件设计,支持生命周期过期(正好匹配 30~90 天留存)。视频文件的归宿,配合 SQLite/LMDB 存元数据。
    • NATS JetStream —— 单二进制消息系统,JetStream 提供持久化、at-least-once、重放。场景一的写入缓冲层(设备 → JetStream → 批量落库);也能做场景二「摄像头 → 存储 → 处理」的事件通知总线。

    E. 更重(为什么不选)

    • ClickHouse —— 分析能力炸裂,但对你的规模偏重,且偏 OLAP 而非实时 ingestion;场景一若要做复杂聚合报表可备选。
    • TimescaleDB / PostgreSQL / MySQL —— 需要运行时 + 初始化 + 服务账号,违反「不能装太多依赖」的约束,列出来只是说明为什么 pass。

    横向对比

    评分用文字,避免看图说话:极高 / 高 / 中 / 可用 / 偏弱 / 不适用。

    产品类型并发模型部署 footprint场景一·心跳场景二·视频关键备注
    SQLite(WAL 调优) 嵌入式 单写者 + WAL 并发读 零依赖·单文件 极高 先调优,多数情况够用
    LMDB 嵌入式 KV MVCC,读不阻塞写,单写 单 C 库 极高 读性能极强,写吞吐一般
    RocksDB 嵌入式 KV LSM,高写吞吐,单写 C++ 库 写洪流进程内首选
    bbolt / sled 嵌入式 KV 单写 + 多读 Go / Rust 库 按技术栈选
    DuckDB 嵌入式 OLAP 进程内并发写(append 无冲突),多进程只读 单 C 库·零依赖 不适用 分析查询强,非主存储
    libSQL SQLite fork 单写者(继承) 嵌入式 + HTTP server 加副本/远程,不改写并发
    rqlite / dqlite 分布式 SQLite 写串行过 leader(HA) 单二进制(Go)/ C 库 可用 为高可用,非写吞吐
    VictoriaMetrics 单二进制 TSDB 高并发写 + 列存压缩 单个静态二进制 极高 不适用 场景一首选
    QuestDB / InfluxDB 单二进制 TSDB 高并发写 单二进制 极高 不适用 场景一备选
    NATS JetStream 单二进制 消息 高吞吐 pub/sub + 持久 单二进制 做写入缓冲 / 事件总线
    MinIO 单二进制 对象存储 高吞吐 blob 单二进制 不适用 极高 场景二视频归宿

    决策树

    照着走就行,不用硬记:

    #mermaid-svg-uph37iMzduypZtnc{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uph37iMzduypZtnc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uph37iMzduypZtnc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uph37iMzduypZtnc .error-icon{fill:#552222;}#mermaid-svg-uph37iMzduypZtnc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uph37iMzduypZtnc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uph37iMzduypZtnc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uph37iMzduypZtnc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uph37iMzduypZtnc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uph37iMzduypZtnc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uph37iMzduypZtnc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uph37iMzduypZtnc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uph37iMzduypZtnc .marker.cross{stroke:#333333;}#mermaid-svg-uph37iMzduypZtnc svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uph37iMzduypZtnc p{margin:0;}#mermaid-svg-uph37iMzduypZtnc .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-uph37iMzduypZtnc .cluster-label text{fill:#333;}#mermaid-svg-uph37iMzduypZtnc .cluster-label span{color:#333;}#mermaid-svg-uph37iMzduypZtnc .cluster-label span p{background-color:transparent;}#mermaid-svg-uph37iMzduypZtnc .label text,#mermaid-svg-uph37iMzduypZtnc span{fill:#333;color:#333;}#mermaid-svg-uph37iMzduypZtnc .node rect,#mermaid-svg-uph37iMzduypZtnc .node circle,#mermaid-svg-uph37iMzduypZtnc .node ellipse,#mermaid-svg-uph37iMzduypZtnc .node polygon,#mermaid-svg-uph37iMzduypZtnc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uph37iMzduypZtnc .rough-node .label text,#mermaid-svg-uph37iMzduypZtnc .node .label text,#mermaid-svg-uph37iMzduypZtnc .image-shape .label,#mermaid-svg-uph37iMzduypZtnc .icon-shape .label{text-anchor:middle;}#mermaid-svg-uph37iMzduypZtnc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uph37iMzduypZtnc .rough-node .label,#mermaid-svg-uph37iMzduypZtnc .node .label,#mermaid-svg-uph37iMzduypZtnc .image-shape .label,#mermaid-svg-uph37iMzduypZtnc .icon-shape .label{text-align:center;}#mermaid-svg-uph37iMzduypZtnc .node.clickable{cursor:pointer;}#mermaid-svg-uph37iMzduypZtnc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uph37iMzduypZtnc .arrowheadPath{fill:#333333;}#mermaid-svg-uph37iMzduypZtnc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uph37iMzduypZtnc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uph37iMzduypZtnc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uph37iMzduypZtnc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uph37iMzduypZtnc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uph37iMzduypZtnc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uph37iMzduypZtnc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uph37iMzduypZtnc .cluster text{fill:#333;}#mermaid-svg-uph37iMzduypZtnc .cluster span{color:#333;}#mermaid-svg-uph37iMzduypZtnc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uph37iMzduypZtnc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uph37iMzduypZtnc rect.text{fill:none;stroke-width:0;}#mermaid-svg-uph37iMzduypZtnc .icon-shape,#mermaid-svg-uph37iMzduypZtnc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uph37iMzduypZtnc .icon-shape p,#mermaid-svg-uph37iMzduypZtnc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uph37iMzduypZtnc .icon-shape .label rect,#mermaid-svg-uph37iMzduypZtnc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uph37iMzduypZtnc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uph37iMzduypZtnc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uph37iMzduypZtnc :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    否 / 已调优仍不够

    能接受(推荐)

    能接受(推荐)

    必须纯进程内零额外进程

    写洪流

    读重 / last-value

    视频元数据

    需要

    单机即可

    瓶颈是架构层还是 SQLite 硬伤?

    每条都 autocommit + FULL + 无 WAL?

    §1 WAL 调优:单写攒批 + busy_timeout

    能否接受多跑一个单二进制 sidecar?

    场景一:VictoriaMetrics + 可选 NATS 缓冲

    场景二:MinIO + SQLite 元数据

    读写特征?

    RocksDB / DuckDB / SQLite+WAL

    LMDB

    SQLite / LMDB / bbolt + 文件系统

    需要多机 HA / 容灾?

    rqlite / dqlite(提升可用性,写仍串行)

    前述选型直接落地

    分场景推荐架构

    场景一:IoT 心跳监控

    #mermaid-svg-xkCUgCLSSVccSO3T{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xkCUgCLSSVccSO3T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xkCUgCLSSVccSO3T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xkCUgCLSSVccSO3T .error-icon{fill:#552222;}#mermaid-svg-xkCUgCLSSVccSO3T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xkCUgCLSSVccSO3T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xkCUgCLSSVccSO3T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xkCUgCLSSVccSO3T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xkCUgCLSSVccSO3T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xkCUgCLSSVccSO3T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xkCUgCLSSVccSO3T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xkCUgCLSSVccSO3T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xkCUgCLSSVccSO3T .marker.cross{stroke:#333333;}#mermaid-svg-xkCUgCLSSVccSO3T svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xkCUgCLSSVccSO3T p{margin:0;}#mermaid-svg-xkCUgCLSSVccSO3T .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xkCUgCLSSVccSO3T .cluster-label text{fill:#333;}#mermaid-svg-xkCUgCLSSVccSO3T .cluster-label span{color:#333;}#mermaid-svg-xkCUgCLSSVccSO3T .cluster-label span p{background-color:transparent;}#mermaid-svg-xkCUgCLSSVccSO3T .label text,#mermaid-svg-xkCUgCLSSVccSO3T span{fill:#333;color:#333;}#mermaid-svg-xkCUgCLSSVccSO3T .node rect,#mermaid-svg-xkCUgCLSSVccSO3T .node circle,#mermaid-svg-xkCUgCLSSVccSO3T .node ellipse,#mermaid-svg-xkCUgCLSSVccSO3T .node polygon,#mermaid-svg-xkCUgCLSSVccSO3T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xkCUgCLSSVccSO3T .rough-node .label text,#mermaid-svg-xkCUgCLSSVccSO3T .node .label text,#mermaid-svg-xkCUgCLSSVccSO3T .image-shape .label,#mermaid-svg-xkCUgCLSSVccSO3T .icon-shape .label{text-anchor:middle;}#mermaid-svg-xkCUgCLSSVccSO3T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xkCUgCLSSVccSO3T .rough-node .label,#mermaid-svg-xkCUgCLSSVccSO3T .node .label,#mermaid-svg-xkCUgCLSSVccSO3T .image-shape .label,#mermaid-svg-xkCUgCLSSVccSO3T .icon-shape .label{text-align:center;}#mermaid-svg-xkCUgCLSSVccSO3T .node.clickable{cursor:pointer;}#mermaid-svg-xkCUgCLSSVccSO3T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xkCUgCLSSVccSO3T .arrowheadPath{fill:#333333;}#mermaid-svg-xkCUgCLSSVccSO3T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xkCUgCLSSVccSO3T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xkCUgCLSSVccSO3T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xkCUgCLSSVccSO3T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xkCUgCLSSVccSO3T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xkCUgCLSSVccSO3T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xkCUgCLSSVccSO3T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xkCUgCLSSVccSO3T .cluster text{fill:#333;}#mermaid-svg-xkCUgCLSSVccSO3T .cluster span{color:#333;}#mermaid-svg-xkCUgCLSSVccSO3T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xkCUgCLSSVccSO3T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xkCUgCLSSVccSO3T rect.text{fill:none;stroke-width:0;}#mermaid-svg-xkCUgCLSSVccSO3T .icon-shape,#mermaid-svg-xkCUgCLSSVccSO3T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xkCUgCLSSVccSO3T .icon-shape p,#mermaid-svg-xkCUgCLSSVccSO3T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xkCUgCLSSVccSO3T .icon-shape .label rect,#mermaid-svg-xkCUgCLSSVccSO3T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xkCUgCLSSVccSO3T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xkCUgCLSSVccSO3T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xkCUgCLSSVccSO3T :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    10,000 IoT 设备每 30s 心跳

    NATS JetStream持久化缓冲 · 削峰填谷

    VictoriaMetrics vmsingle单二进制 · 7d 留存

    DashboardGrafana / 自研

    设备心跳 → NATS JetStream(持久化、抗尖峰)→ 消费者批量写入 VictoriaMetrics(vmsingle,-retentionPeriod=7)→ 仪表盘用 MetricsQL 查。心跳写入走 InfluxDB 行协议:

    heartbeat,device=iot-001,port=8080 status=1,latency_ms=12

    不想引入 NATS 的话,可以简化成「设备直接批量写 VM」,或者退回「SQLite WAL 调优」兜底。我倾向于小流量先 SQLite 顶着,真看到瓶颈再上 VM,没必要一上来就铺。

    场景二:摄像头短视频

    #mermaid-svg-J6qZoCjkx93XjHnA{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-J6qZoCjkx93XjHnA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J6qZoCjkx93XjHnA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J6qZoCjkx93XjHnA .error-icon{fill:#552222;}#mermaid-svg-J6qZoCjkx93XjHnA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J6qZoCjkx93XjHnA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J6qZoCjkx93XjHnA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J6qZoCjkx93XjHnA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J6qZoCjkx93XjHnA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J6qZoCjkx93XjHnA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J6qZoCjkx93XjHnA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J6qZoCjkx93XjHnA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J6qZoCjkx93XjHnA .marker.cross{stroke:#333333;}#mermaid-svg-J6qZoCjkx93XjHnA svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J6qZoCjkx93XjHnA p{margin:0;}#mermaid-svg-J6qZoCjkx93XjHnA .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-J6qZoCjkx93XjHnA .cluster-label text{fill:#333;}#mermaid-svg-J6qZoCjkx93XjHnA .cluster-label span{color:#333;}#mermaid-svg-J6qZoCjkx93XjHnA .cluster-label span p{background-color:transparent;}#mermaid-svg-J6qZoCjkx93XjHnA .label text,#mermaid-svg-J6qZoCjkx93XjHnA span{fill:#333;color:#333;}#mermaid-svg-J6qZoCjkx93XjHnA .node rect,#mermaid-svg-J6qZoCjkx93XjHnA .node circle,#mermaid-svg-J6qZoCjkx93XjHnA .node ellipse,#mermaid-svg-J6qZoCjkx93XjHnA .node polygon,#mermaid-svg-J6qZoCjkx93XjHnA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J6qZoCjkx93XjHnA .rough-node .label text,#mermaid-svg-J6qZoCjkx93XjHnA .node .label text,#mermaid-svg-J6qZoCjkx93XjHnA .image-shape .label,#mermaid-svg-J6qZoCjkx93XjHnA .icon-shape .label{text-anchor:middle;}#mermaid-svg-J6qZoCjkx93XjHnA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J6qZoCjkx93XjHnA .rough-node .label,#mermaid-svg-J6qZoCjkx93XjHnA .node .label,#mermaid-svg-J6qZoCjkx93XjHnA .image-shape .label,#mermaid-svg-J6qZoCjkx93XjHnA .icon-shape .label{text-align:center;}#mermaid-svg-J6qZoCjkx93XjHnA .node.clickable{cursor:pointer;}#mermaid-svg-J6qZoCjkx93XjHnA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J6qZoCjkx93XjHnA .arrowheadPath{fill:#333333;}#mermaid-svg-J6qZoCjkx93XjHnA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J6qZoCjkx93XjHnA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J6qZoCjkx93XjHnA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J6qZoCjkx93XjHnA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J6qZoCjkx93XjHnA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J6qZoCjkx93XjHnA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J6qZoCjkx93XjHnA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J6qZoCjkx93XjHnA .cluster text{fill:#333;}#mermaid-svg-J6qZoCjkx93XjHnA .cluster span{color:#333;}#mermaid-svg-J6qZoCjkx93XjHnA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-J6qZoCjkx93XjHnA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J6qZoCjkx93XjHnA rect.text{fill:none;stroke-width:0;}#mermaid-svg-J6qZoCjkx93XjHnA .icon-shape,#mermaid-svg-J6qZoCjkx93XjHnA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J6qZoCjkx93XjHnA .icon-shape p,#mermaid-svg-J6qZoCjkx93XjHnA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J6qZoCjkx93XjHnA .icon-shape .label rect,#mermaid-svg-J6qZoCjkx93XjHnA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J6qZoCjkx93XjHnA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J6qZoCjkx93XjHnA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J6qZoCjkx93XjHnA :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    拉取视频

    摄像头每 30s 一段

    MinIO S3 对象存储key: cam/date/time.mp4ILM 自动过期 30-90 天

    处理 Worker转码 / 识别

    SQLite / LMDB 元数据路径/摄像头/时长/状态

    CMDB 面板只查元数据

    摄像头写 30s 片段 → MinIO(S3,key 按 摄像头/日期/时间.mp4 组织,配生命周期规则自动删 30~90 天前的片段)→ 处理 Worker 拉视频做转码 / 识别 → 结果(缩略图、检测结果)回写 → SQLite 存元数据(路径、摄像头、时长、处理状态)。面板只查元数据,绝不扫视频本体。

    落地配置示例

    ① SQLite WAL 调优(零成本兜底,两场景通用)

    — 连接初始化(各语言 SDK 都支持 PRAGMA)
    PRAGMA journal_mode=WAL;
    PRAGMA synchronous=NORMAL;
    PRAGMA busy_timeout=5000;
    — 写入:单写协程 + 每批 1000 条一个事务
    — 设备端打点加 jitter:sleep(random(0, 4000))ms,错峰

    ② VictoriaMetrics 单节点(场景一上量方案)

    # 下载单个二进制后直接运行
    ./victoria-metrics-prod -retentionPeriod=7 -storageDataPath=./vmdata

    # 设备/网关用 InfluxDB 行协议写入(HTTP POST :8428/write)
    heartbeat,device=iot-001,port=8080 status=1,latency_ms=12 1717833600000000000

    # 查询最新状态(MetricsQL)
    last_over_time(heartbeat{device="iot-001"}[5m])

    # 趋势图(自动降采样)
    count_over_time(heartbeat[1h])

    ③ MinIO 对象存储(场景二视频归宿)

    # 单个二进制起服务
    minio server /data/videos –console-address :9001

    # 上传(对象 key 即元数据里的 path)
    mc cp clip_20260708_120000.mp4 local/cam-01/2026/07/08/

    # 元数据表(SQLite / LMDB 均可)
    CREATE TABLE clip_meta (
    id INTEGER PRIMARY KEY,
    cam TEXT, start_ts INTEGER, end_ts INTEGER,
    obj_key TEXT, size INTEGER, status TEXT
    );

    落地清单

    • 先量后换: 上线 WAL 调优,用真实流量压 24 小时,确认 QPS / 延迟达标再决定要不要引 sidecar。
    • 写入侧加缓冲: 不管选哪个库,入口都放一个内存队列 + 单写协程,把并发写变成串行批量写。
    • 设备端错峰: 心跳时间戳加 ±几秒随机 jitter,消掉 :00/:30 的万级尖峰。
    • 场景二绝不存视频进库: 视频只在 MinIO / 文件系统,库里只留 obj_key 字符串。

    风险与权衡

    老实说几个坑:

    • 嵌入式 ≠ 多写者。 LMDB / RocksDB / DuckDB / libSQL / bbolt 本质都是单写者。所谓「并发」多是「多读 + 单写」或「进程内无冲突并发」。如果你真需要多个独立进程同时高频写,纯嵌入路线无解,得上单二进制服务(VM/QuestDB)或分布式层(rqlite,但写仍串行)。
    • 单二进制也是要运维的。 VictoriaMetrics / MinIO 虽是一个文件,但仍是一个独立进程:要管启动、监控、磁盘、升级。好处是远比 PG/MySQL 轻——无运行时、无初始化、无账号体系。动手前先确认你的「不能装依赖」包不包含「可跑一个 sidecar」。
    • 视频体量要算账。 20 路 × 90 天 ≈ 5~15 TB。确认磁盘容量和生命周期过期策略(MinIO ILM 或脚本清理),否则存储会悄悄涨满。冷数据可以迁到更大但更慢的盘。
    • DuckDB 别当主库。 DuckDB 强在分析查询,多进程写入不支持。它适合做「SQLite/VM 存原始,DuckDB 做离线报表」,而不是 ingestion 主存储。

    最后给个优先级:① 先 SQLite WAL 调优(零成本,两场景元数据都覆盖);② 场景一若上量,VictoriaMetrics(单二进制)+ 可选 NATS 缓冲;③ 场景二 MinIO + SQLite 元数据。全程不碰 PG/MySQL,轻量约束守住。

    参考来源

    • SQLite WAL 官方文档
    • libSQL GitHub
    • VictoriaMetrics GitHub
    • DuckDB 并发模型
    • MinIO 官网
    • NATS JetStream 文档
    • QuestDB 官网
    • LMDB (Symas)

    基于公开产品文档与社区实践整理(2026-07)。产品能力持续演进,落地前请以各项目最新官方文档为准。

    赞(0)
    未经允许不得转载:171主机测评 » SQLite 顶不住了?本地轻量服务该怎么选数据库
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址