Colmap 进军嵌入式:SQLite 数据库从崩溃退出到自动治愈

图谱后端选型:Memgraph vs Neo4j vs 嵌入式 SQLite,什么时候该切换? 本文通过脚本自动探测代码图谱规模,对比SQLite、Memgraph与Neo4j在不同节点量级下的性能表现。实测表明:10万节点内嵌入式SQLite凭借低延迟和轻量部署仍具竞争力;超15万节点后,Memgraph适合低延迟、高并发场景;而Neo4j仅在百万级图谱且需复杂查询时才具备优势。配套提供一键测试延迟与平滑迁移后端的Python工具链,实现按需选型与无缝切换。 阅读详情

Colmap 进军嵌入式:SQLite 数据库从崩溃退出到自动治愈

场景:将 Colmap 离线 SfM 工具链移植到嵌入式平台的实时SLAM系统中,数据库因断电、SD 老化等异常导致损坏,程序直接崩溃退出。在线系统优先保证可运行,因此必须识别到数据库损坏的情况,进行提前处理,避免崩溃。本文记录了从问题发现到完整修复方案的工程实践。
有懂数据库的大佬,可以指导更加优雅的做法。


一、问题背景:离线与在线的本质差异

1.1 Colmap 的原始设计假设

Colmap 作为离线 SfM 工具链,其数据库操作层有一个"简单粗暴"的错误处理策略:

// colmap/src/util/sqlite3_utils.h
inline int SQLite3CallHelper(const int result_code, ...) {
  switch (result_code) {
    case SQLITE_OK:
    case SQLITE_ROW:
    case SQLITE_DONE:
      return result_code;
    default:
      fprintf(stderr, "SQLite error ...");
      exit(EXIT_FAILURE);   // ← 直接终止进程!
  }
}

#define SQLITE3_CALL(func) SQLite3CallHelper(func, __FILE__, __LINE__)

这个设计在离线场景下完全合理:

  • 用户在桌面端运行重建任务
  • 崩溃了无非重新跑一次
  • 数据可以随时备份恢复
  • 开发效率优先于容错

但在 嵌入式实时系统中,这个设计是致命的:

维度离线 SfM (Colmap 原生)实时SLAM
运行环境PC / 服务器嵌入式平台
可靠性要求崩溃可接受全程不中断
数据来源用户主动导入自动采集,无人值守
存储介质稳定硬盘SD 卡 / eMMC(易老化)
异常场景少见频繁断电、存储满、坏块
错误处理exit() 终止必须自愈恢复

1.2 生产环境中的真实故障

在一次实际运行中,设备异常断电后重启,日志显示:

SQLite error [database.cc, line 276]: disk I/O error (code: 5386)
SQLite error [database.cc, line 300]: database disk image is malformed
SQLite error [database.cc, line 303]: database disk image is malformed
...后续所有 SQL 操作全部失败...

根因分析:

  1. sqlite3_open_v2() 返回了非 SQLITE_OK 的错误码 → SQLITE3_CALL 宏触发 exit(EXIT_FAILURE)
  2. 即使 open "成功"返回 SQLITE_OK(SQLite 对某些损坏文件会宽容处理),后续 PRAGMA 和 SQL 操作也会因为读取到损坏页面而报 malformed

核心矛盾:离线场景下退出后,用户介入再次执行,而嵌入式平台在线运行时,导致程序崩溃,无法介入,后果很严重。


二、技术深潜:SQLite

2.1 为什么"不关闭就修改文件"检测不到损坏?

这是调试过程中最容易踩的坑:

// ❌ 错误做法:db 还活着就去破坏文件
{
    Database db(db_path);       // sqlite3_open_v2() 打开数据库
    db.WriteCamera(camera);      // 数据写入 WAL 文件
    
    // 此时 db 对象还活着!
    CorruptDatabase(db_path);    // 用 fstream 覆盖前 64 字节
    
    Database db2(db_path);       // 第二次打开 —— 居然成功了?!
}

原因:三层缓存机制

┌─────────────────────────────────────────────────────────────────┐
│                    SQLite 连接内部状态                           │
│                                                                 │
│  ┌─────────────┐     ┌──────────────┐     ┌────────────────┐  │
│  │ Page Cache  │ ←── │ OS Page Cache│ ←── │  磁盘 .db 文件 │  │
│  │ (SQLite层)  │     │  (内核层)     │     │                │  │
│  │             │     │              │     │ [0xAB 0xAB ...]│  │
│  │ page 0: 合法│     │ fd_A 的缓存   │     │  (被 fstream   │  │
│  │ header 页   │     │              │     │   覆盖了)       │  │
│  └─────────────┘     └──────────────┘     └────────────────┘  │
│        ↑                   ↑                    ↑             │
│        │ db 的 fd_A        │                    │ fstream 的    │
│        │ 从这里读           │                    │ fd_B 写到这里  │
└─────────────────────────────────────────────────────────────────┘

关键点:

  • db 的连接持有独立的 file descriptor (fd_A),其 page cache 中已有合法的 header 页
  • CorruptDatabase() 用全新的 fstream 打开 (fd_B),直接写磁盘
  • 但 db 的后续操作优先从自己的 page cache 读取,根本不碰磁盘
  • 当 db2.Open() 时,通过 WAL 文件的协调机制,仍能读到一致的状态

这点还未经验证,暂且相信

2.2 sqlite3_open_v2() 对不同损坏的反应

损坏类型返回值能否被捕获?
Header 被 0xAB 覆盖SQLITE_OK(只创建句柄,不验证内容)✅ 需要 PRAGMA quick_check
文件截断到 100 字节SQLITE_OK(同上)✅ quick_check 报 “not a database”
磁盘坏块 / I/O 错误SQLITE_IOERR❌ SQLITE3_CALL 直接 exit!
权限丢失SQLITE_CANTOPEN❌ 同上
WAL 文件损坏但主 DB 正常SQLITE_OK⚠️ 取决于后续操作

结论:对于 I/O 类错误,检查逻辑必须在 sqlite3_open_v2() 之前执行。


三、解决方案设计

3.1 核心思路:预检查模式

  DatabaseFileHealthyCheck(path)  ← 新增!独立预检查
       │
       ├─ 健康 → 继续
       └─ 损坏 → 备份 + 清理 → 删除旧文件
                        ↓
  Open() → sqlite3_open_v2() → 创建全新空库 → 成功 ✓

3.2 接口设计

// database.h — 对外暴露的唯一新接口
class Database {
public:
    // 原有接口保持不变
    void Open(const std::string& path);
    
    /**
     * 静态方法:在 Open() 之前预检查数据库文件健康状态
     * 
     * 设计要点:
     * 1. 用临时独立连接(SQLITE_OPEN_READONLY)探测,不影响主流程
     * 2. 不使用 SQLITE3_CALL 宏,避免 IOERR 时直接 exit
     * 3. 检测到损坏时自动备份 + 清理 WAL/SHM + 删除原文件
     * 4. 让后续 Open() 自然创建空库
     * 
     * @return true  文件健康或首次创建
     * @return false  文件曾损坏但已清理(调用方可据此决定是否报警)
     */
    static bool DatabaseFileHealthyCheck(const std::string& path);

};

3.4 核心实现

// database.cc — DatabaseFileHealthyCheck 实现
bool Database::DatabaseFileHealthyCheck(const std::string& path) {
    // 1. 快速路径:文件不存在说明是首次创建
    if (!boost::filesystem::exists(path)) {
        return true;
    }

    // 2. 用临时独立连接打开(只读模式,避免影响主流程)
    sqlite3* temp_db = nullptr;
    const int rc = sqlite3_open_v2(
        path.c_str(), &temp_db,
        SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX,
        nullptr);

    bool is_healthy = false;

    // 3. 即使 open 失败也继续检查(IOERR/CANTOPEN 都算损坏)
    if (rc != SQLITE_OK) {
        std::cerr << "[Ensure] Cannot open for health check (rc=" << rc << ")" << std::endl;
    }

    if (temp_db != nullptr) {
        // 4. 执行完整性校验
        sqlite3_stmt* stmt = nullptr;
        int check_rc = sqlite3_prepare_v2(temp_db, "PRAGMA quick_check;", -1, &stmt, nullptr);

        if (check_rc == SQLITE_OK) {
            std::string result;
            while (sqlite3_step(stmt) == SQLITE_ROW) {
                const char* text = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
                if (text != nullptr) result = text;
            }
            is_healthy = (result == "ok");
            sqlite3_finalize(stmt);
        }
        sqlite3_close_v2(temp_db);
    }

    // 5. 损坏则备份并清理
    if (!is_healthy || rc != SQLITE_OK) {
        //BackupAndRebuild
        return false;  // 表示曾损坏
    }

    return true;  // 健康
}

四、价值总结

4.1 改造带来的收益

收益维度具体表现
系统可用性断电/SD 老化后自动恢复,无需人工干预
运维成本减少现场排查和手动修复数据库的工作量
数据安全损坏的数据库自动带时间戳备份(.corrupted_xxx.db),保留事后分析的可能性
代码质量Open() 接口保持原始状态不变,降低回归风险
架构清晰预检查逻辑独立为静态方法,职责单一,易于测试

4.2 改造的代价

代价应对措施
额外一次 sqlite3_open_v2 调用只读模式,开销 < 5ms;且只在文件已存在时才执行
重建后原有数据丢失这是设计决策:在线系统优先保证可运行,而非数据恢复(数据可通过地图服务重新下发)
需要维护额外的测试用例5 个测试用例覆盖主要场景,维护成本低

五、知识点索引

知识点应用场景参考资料
SQLite WAL 模式理解 page cache、checkpoint 机制SQLite WAL 文档
PRAGMA quick_check vs integrity_check快速校验 vs 完全校验的性能权衡SQLite PRAGMA 文档
sqlite3_open_v2 行为不同错误码对应的损坏类型判断SQLite C API
OS Page Cache 与 fd 缓存隔离解释"不 close 就修改检测不到损坏"现象Linux man 2 read
SQLITE_OPEN_READONLY vs READWRITE预检查用只读模式避免副作用SQLite flags 定义
boost::filesystem 跨平台文件操作文件大小获取、rename、removeBoost.Filesystem
SpringBoot+Vue+MySQL 高校学科竞赛平台 完整可运行版(源码+文档) 本项目基于SpringBoot+Vue+MySQL构建高校学科竞赛管理平台,覆盖学生、教师、领队教师、管理员四大角色,实现竞赛发布、报名、刷题、评分、公示等全流程闭环管理。系统架构清晰,技术主流,配套完整源码、数据库脚本、万字论文、开题报告、答辩PPT及部署文档,支持一键运行,适用于毕业设计、课程设计及院系二次开发,真正实现“开箱即用”。 阅读详情

相关推荐

电池保护板为什么采用NTC热敏电阻而不是PTC热敏电阻?

R25=10K,即环境温度25°时,阻值是10K),温度变化1°C,阻值相应地明显变化,温度响应很灵敏,也就能精准地反向换算出阻值对应的温度。另外,还有功率型NTC热敏电阻,其主要用在开关电源设计中,串联在市电输入上,抑制浪涌电流。电池保护板上的NTC热敏电阻(NTC一端接地,另一端接电池充电控制IC管脚),在电池温度上升时,NTC热敏电阻温度也会随之上升,从而NTC电阻值会下降,因此电池充电控制IC用ADC检测的电压值就降低,当检测到的电压值达到下限阈值时,电池充电控制IC就会停止对电池的充电或放电。

电子绿洲的博客 5628

Spring Boot 数据源与 MyBatis(-Plus) 自定义配置详解

本文详解 Spring Boot 中单数据源环境下 HikariCP 与 MyBatis-Plus 的自定义配置。通过手动声明 SqlSessionFactory,实现下划线转驼峰、日志输出、一级缓存策略等精细化控制。结合 HikariCP 核心参数调优(如最大连接数、连接生命周期),确保高性能与稳定性。适用于仅需访问单一数据库的场景,避免多数据源复杂性,兼顾性能与可维护性。

BADAO_LIUMANG_QIZHI的博客 208

ANSYS MAXWELL内置式永磁同步电机(IPMSM)转矩最大时输入电流相位的确定

1.在转子转速为零时看一个周期内的转矩变化

qq_41752507的博客 6517

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

要努力去发光,而不是被照亮~ 147

STM32F103C8T6与OV7670摄像头在TFT屏上的实时数字识别系统实现

本文详细介绍了基于STM32F103C8T6单片机和OV7670摄像头实现实时数字识别系统的全过程。系统通过图像采集、预处理、二值化和模板匹配算法,在TFT屏上实时显示识别结果,并针对资源受限的单片机环境提供了内存与性能优化策略,适用于智能家居、工业仪表读数等低成本嵌入式视觉应用场景。

open4的博客 278

电子工程师必看:2024年IPC-A-610J和J-STD-001J新标准到底更新了啥?

本文深入解析2024年IPC-A-610J和J-STD-001J新标准的核心更新,重点关注电子制造领域的验收标准与工艺参数变化。新版标准强化了高密度组装和微焊接工艺的规范,包括焊料润湿角调整、BGA空洞率严格化等关键变更,为电子工程师提供实用指南。同时关联IPC-7711/21D和IPC-2221C的配套更新,助力企业快速适应新标准要求。

weixin_33694620的博客 784

Docker Compose:搭建本地多服务开发环境

从一个 Java API、PostgreSQL 和 Redis 开始,配置服务名、端口、环境变量、健康检查、数据卷和依赖关系。理解 Compose 文件如何被解析、项目如何隔离,以及哪些数据不能依赖“重建容器”保留,能够把个人开发环境整理成团队可复用的启动入口。

m0_62221377的博客 462

【AI蛋白预测实战】零门槛在Colab上运行AlphaFold2全流程解析

本文为零基础用户提供了在谷歌Colab平台上运行AlphaFold2进行蛋白质结构预测的完整实战指南。通过ColabFold项目,用户无需本地高性能硬件,即可免费利用云端GPU完成从序列输入到3D结构生成和结果可视化的全流程,大幅降低了AI蛋白预测的技术门槛。

mqtt6iot的博客 913

AI全栈实战 | 3.9-01 爬虫入门:requests 加 BeautifulSoup 三分钟跑通,但 90% 的人栽在这 4 个“礼貌“问题上

⚠️:本文内容仅用于。请遵守目标站点的服务条款与相关法律法规,不得对目标服务造成负担,不得采集、传播个人隐私数据。技术是中性的,边界在你手里。爬虫的入门技术(requests 发请求 + BeautifulSoup 解析)三分钟就能学会。真正区分"会用爬虫"和"会做爬虫的人"的,是这篇我们跑通"请求→解析→存储"的最小闭环,然后重点把"合规与礼貌"这根弦给你绷上。

源码获取 | 运行部署 | 定制二开修改,如果回复不及时请耐心等待,请见谅。 422

芯片详解——AD7606

AD7606详解

weixin_45661757的博客 5022

图形引擎实战:镭射效果制作分享

该方案的优势1.遵循物理的2.可以使用少量的参数去调控镭射的效果。计算相对简单,缺点仍是颜色不能够自定义,还有一种常见的方案是采样一张镭射贴图来模拟,好处是颜色可以自定义同时性能上还很高效没有复杂的公式计算。我查阅了网上普遍的方案,常见的做法是制作一个Ramp图生成工具,然后将生成好的ramp图赋给材质。这两个方案的好处是美术同学可以手动的去控制镭射的颜色。最后就是Shader中对Ramp图的采样的部分,Ramp图的UV坐标是使用fresnel作为Ramp的U轴坐标,0.5作为V轴坐标。

qq_41166022的博客 1359

数据库主键选型指南:BIGINT vs VARCHAR

主键选型关乎数据库性能与架构演进。BIGINT(如雪花算法)在顺序写入、二级索引开销和分布式生成上优势显著,适合高并发核心链路;而传统 VARCHAR UUIDv4 因随机性导致频繁页分裂、存储膨胀,性能堪忧。UUIDv7 通过时间戳有序性实现高效追加写入,结合 BINARY(16) 存储可兼顾性能与安全,成为现代微服务新选择。关键在于:主键应稳定、无业务含义,优先选用趋势递增的紧凑类型,并根据场景合理采用双主键模式或 ULID 等替代方案。

小李同学的博客 306

用 Claude Code 快速摸清一个陌生代码库

摘要:Claude Code作为AI编码助手,能帮助开发者快速理解陌生代码库。与传统代码搜索不同,它从项目根目录出发,通过分析目录结构、配置文件等技术栈信息,提供系统级的概览。最佳实践是先通过"give me an overview"获取整体架构,再深入数据模型、认证流程等核心模块。它能生成术语表、编码规范和推荐的代码阅读路径,极大降低新成员的学习成本。相比传统方式,Claude Code能主动探索项目上下文,无需人工整理相关文件,使代码理解过程更接近有经验的工程师带教。(149字)

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 94

【基于 WebSocket 的多用户网页五子棋】—— 数据管理模块

但行好事,莫问前程

qq_42996461的博客 229

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

自动化运维选型:四条技术路线的能力边界,以及它们为什么不能互相替代

自动化运维怎么选型?拆解配置管理、基础设施即代码、作业调度与场景化编排四条技术路线边界,分析账本归属与时间语义差异,给出四种架构组合与判断点,帮企业看清为何不能互相替代。

trainsignalcn的专栏 340

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

C++算法(七)

C++ stack算法题和一些客观题

2503_94479566的博客 374

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

不用再为文本搜索单独维护一套系统!全文检索深度解析

为了给商品、客服工单或者企业知识库增加一个搜索框,企业往往需要在业务数据库之外,再部署一套Elasticsearch。数据库保存商品、订单和用户等真实业务数据,搜索引擎保存另一份文本索引;两套系统之间,还需要通过CDC、消息队列或者同步程序持续传输数据。一个看似简单的搜索需求,最后可能变成两套集群、两份数据和一条必须保持稳定的数据链路。由此产生的问题并不少:数据库已经完成更新,搜索结果却可能尚未同步;业务查询同时涉及文本、库存、权限和订单状态时,应用需要在两个系统之间来回组合结果;

TiDB 社区干货传送门 290

IDEA 简介:从零认识 IntelliJ IDEA

IDEA 全称,是由 JetBrains 公司开发的一款主流 Java 集成开发环境(IDE)。它集代码编辑、编译、调试、测试、版本控制、构建管理等功能于一体,被大量 Java 开发者视为日常开发的首选工具。所谓 IDE(Integrated Development Environment,集成开发环境),就是把写代码所需的各类工具整合到同一个界面中。相比只用文本编辑器加命令行编译,IDE 能显著减少重复操作,让开发者把精力集中在业务逻辑上。

ztI10的博客 163

市场调查与统计分析专业大学期间考证怎么安排?

市场调查与统计分析专业的考证规划,适合从一份完整的调查报告倒推:能不能设计问卷、判断样本是否合理、清洗数据,再把分析结果转化为业务建议?围绕这条能力链安排证书,比单纯按“热门程度”报名更有针对性。结合2026年的学习安排,并为2027年的实习、升学和求职预留时间,大学期间可以重点考虑六类证书:全国计算机等级考试证书、大学英语四级或六级成绩报告单、CDA数据分析师、初级统计专业技术资格证书、微软办公软件国际认证中的Excel相关证书,以及软考数据库系统工程师资格证书。

cc572502653的博客 165

大表 ALTER TABLE 怎么做?NineData OnlineDDL 配置与执行实践

介绍NineData OnlineDDL大表变更实践,涵盖执行策略、切表控制、拷贝批次与可视化进度追踪。

dbasenewbie的博客 275

Oracle 19c 在线重定义后旧表空间清理及 ASM 存储资源回收 SOP

Oracle 19c 在线重定义后旧表空间清理与 ASM 存储资源回收 SOP

「YYL DBA手记」 数据库 · 架构 · 运维 · 实战 yyldba.blog.youkuaiyun.com 503

MySQL报Too many open files?句柄耗尽的排查与参数配置

文章讲清 max_connections 与 open_files_limit 两个额度、句柄被谁占走、分区表为什么最容易踩,给出从 error log 到 /proc/pid/limits 的定位顺序与句柄预算算法。

「一个在数据库赛道上边学边分享的学妹」——聚焦国产数据库/信创方向、分享学习笔记、踩坑经验和行业观察。 320

Spring事务和事务传播机制

3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.p1事务可以认为是⽗事务, 嵌套事务是⼦事务. ⽗事务出现异常, ⼦事务也会回滚, ⼦事务出现异常, 如果不进⾏处理, 也会导致⽗事务回滚.

txxzjmzlh的博客 207

从事件风暴到因果收敛:Mopheus多智能体协同事件触发架构与防循环实践

在构建生产级多智能体协同引擎的过程中,我们总结了三条核心工程准则:1.不可变因果链优于事后关系反查在高度并发且异步的多智能体网络中,因果关系的追溯绝不能依赖事后查库反查关联;将只增的因果祖先链显式封装在事件载荷与任务上下文中,以O(1)的内存检查实现确定性极高的闭环防范。2.生产端纯粹广播与消费端前置极速过滤是高吞吐的基石遵循严格的Pub/Sub模式,让核心业务实体与下游智能体编排解耦;通过调度层内存条件字典求值(conditions),在纳秒级内过滤掉99%的无关事件,保障零业务侵入与极高的系统吞吐。

weixin_54551388的博客 244

Redis 入门详解:核心特性、重要文件与全局命令实战

本文系统介绍了Redis的核心特性、重要文件及全局命令。Redis凭借内存存储、C语言实现和单线程设计,具备极高的读写性能;支持持久化(RDB/AOF)、主从复制与集群架构,适用于缓存、排行榜、计数器等场景,但不适合存储海量冷数据。文中还解析了常见面试题,如为何快速、单线程原理、持久化机制、KEYS *禁用原因及过期键处理策略,强调其在高并发场景下的优势与局限。

2303_77004089的博客 202

【FDE前沿部署工程师实战教程】第62章:Enterprise AI Developer Platform——企业级 AI 开发者平台与低代码 AI 应用工厂

企业从AI能力平台迈向AI应用生产平台,需构建统一的企业级AI开发者平台,将模型、RAG、Agent、Workflow等能力封装为可视化、可复用的开发工具。通过PromptIDE、AgentBuilder、WorkflowBuilder等模块,实现低代码与专业代码协同,降低开发门槛。平台覆盖应用全生命周期管理,集成测试评估、安全治理与CI/CD发布体系,推动AI应用规模化、标准化交付。最终形成“需求→设计→开发→测试→发布→运营”的企业AI应用工厂模式,助力制造等行业的智能化转型。

IT古董 310
上一篇: SLAM实战避坑笔记:基础矩阵退化场景分析与解决方案
下一篇: SLAM实战避坑指南——C++ 对象生命周期:从内存布局到析构顺序的完整指南
Undergoer_TW
博客等级 码龄11年 185粉丝 103原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值