news 2026/9/10 8:24:52

思源笔记 v3.7.1 技术解读:FTS5 索引去重、空闲自动修索引与移动端体验改进

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
思源笔记 v3.7.1 技术解读:FTS5 索引去重、空闲自动修索引与移动端体验改进

思源笔记 v3.7.1 技术解读:FTS5 索引去重、空闲自动修索引与移动端体验改进

【免费下载链接】siyuanAn open-source, privacy-first, self-hosted knowledge workspace where humans and AI agents work together 开源、隐私优先、自托管的知识工作空间,让人与智能体在此协作项目地址: https://gitcode.com/GitHub_Trending/si/siyuan

导读

思源笔记(SiYuan)v3.7.1 是一个以"细节打磨"为主的版本:blocks_fts全文索引改为 FTS5 external content 模式以消除数据冗余,新增用户空闲时自动订正索引机制,同时修复了 PDF 内文本搜索失效、数据库分组后无法新增字段等一批高价值问题。本文以官方变更日志为骨架,结合本仓库源码逐一剖析这些改动的实现原理、触发条件与影响面,帮助读者理解思源底层索引架构与本次升级的实战价值。

本文内容依据 app/changelogs/v3.7.1/v3.7.1.md,源码分析均来自当前仓库。


一、版本概览与变更结构

v3.7.1 共包含三大类变更:

  • Enhancement(增强):21 项,覆盖全文索引架构、索引维护、集市、Markdown 导入、Electron 行为、移动端交互等;
  • Bugfix(缺陷修复):11 项,重点包括 PDF 搜索、数据库字段过滤、数据库视图同步、安全漏洞等;
  • Document(文档):1 项,新增工作空间文件系统布局文档。

其中,最值得开发者关注的是两项底层架构级改动:

  1. blocks_fts采用 FTS5 external content 模式,与blocks表去重存储;
  2. 用户空闲时自动执行索引订正(index fixing)。

下面逐一深入。


二、全文索引架构升级:blocks_fts 改用 FTS5 external content 模式

2.1 改动目标:与 blocks 表去重

官方变更日志:

Use FTS5 external content mode for blocks_fts to deduplicate data with blocks

思源将每个块的内容同时写入blocks表(结构化数据)与blocks_fts虚拟表(全文检索倒排索引),两个表之间长期存在一份数据的物理冗余。本次升级将blocks_fts切换为 FTS5 的external content 模式:FTS 表不再物理存储各列值,仅维护倒排索引,原始内容由content='blocks'指向的blocks表按rowid回表提供。

2.2 源码实现:建表语句与核心约束

在 kernel/sql/database.go 的initFTSBlocks()中可以看到完整建表逻辑:

// 采用 external content 模式:blocks_fts 不再物理存储列值,仅维护倒排索引, // 原文由 content 指向的 blocks 表提供,按 content_rowid(blocks 的隐式 rowid)回表取值。 // 因此 FTS 行的 rowid 必须与 blocks 行的 rowid 严格一致,所有写路径需显式传 rowid。 _, err = db.Exec("CREATE VIRTUAL TABLE blocks_fts USING fts5(id UNINDEXED, parent_id UNINDEXED, root_id UNINDEXED, hash UNINDEXED, box UNINDEXED, path UNINDEXED, hpath UNINDEXED, name, alias, memo, tag, content, fcontent, markdown UNINDEXED, length UNINDEXED, type UNINDEXED, subtype UNINDEXED, ial, sort UNINDEXED, created UNINDEXED, updated UNINDEXED, content='blocks', content_rowid='rowid', tokenize=\"" + ftsTokenize() + "\")")

关键点解读:

  • content='blocks'声明外部内容表为blockscontent_rowid='rowid'指定以blocks的隐式 rowid 作为回表键;
  • 可检索列(namealiasmemotagcontentfcontential)保持索引,其余元数据列标记为UNINDEXED
  • 分词器由ftsTokenize()动态注入,即思源自研的siyuan分词(支持中英文混合与大小写折叠)。

该模式的核心约束是:FTS 行与blocks行的 rowid 必须严格一一对应,因此所有写入路径都必须显式携带 rowid。这一点在 kernel/sql/upsert.go 的注释与BlocksFTSInsert语句中明确体现:

// blocks_fts 采用 external content 模式(content='blocks'),写入时必须显式提供 rowid, BlocksFTSInsert = "INSERT INTO blocks_fts (rowid, id, parent_id, root_id, hash, box, path, hpath, name, alias, memo, tag, content, fcontent, markdown, length, type, subtype, ial, sort, created, updated) VALUES %s"

同时 kernel/sql/block.go 规定:局部更新索引列的路径(updateRootContentupdateBlockContentindexNode)必须先写blocks_fts、再写blocks,保证回表取值前倒排索引已就绪。

2.3 索引重建方式的变化

external content 模式下,重建索引不能再使用INSERT ... SELECT FROM blocks(那样 FTS 会自分配 rowid 导致与blocks脱钩),必须使用 FTS5 内建命令:

func RebuildFTSIndex() (err error) { if err = initFTSBlocks(); err != nil { return } // external content 模式下使用 'rebuild' 命令重建索引: // FTS5 会扫描 blocks 表,并用 blocks 的 rowid 作为 FTS rowid,保证两者对齐。 stmt := "INSERT INTO blocks_fts(blocks_fts) VALUES('rebuild')" _, err = db.Exec(stmt) return }

对应 kernel/sql/database.go。当手动触发"重建索引"(POST /api/system/rebuildDataIndex,见 kernel/api/router.go)或RebuildFTSIndex失败回退全量重建(kernel/model/box.go)时都会走到这条路径。

2.4 性能基准测试验证

仓库新增了专门的基准测试 kernel/sql/fts_bench_test.go,量化 external content 模式(content='blocks', content_rowid='rowid')与旧标准模式的写入、查询、重建开销差异。测试中对比了两套建表 DDL 与两套写入逻辑(标准模式成对写blocksblocks_fts,external 模式显式带 rowid 写入),可直接作为评估该改造收益的参考依据。

实战提示:本次升级会在启动时重建blocks_ftsinitFTSBlocks会先DROP TABLE IF EXISTS blocks_fts)。升级后首次启动会有一段重建索引时间,属预期行为;索引重建完成后,全文搜索(内容、名称、别名、标签)行为与之前一致,但数据存储冗余显著降低。


三、空闲自动订正索引:AutoFixIndex 的触发与流水线

3.1 改动目标

官方变更日志:

Automatically perform index fixing during idle time

此前索引订正(index fixing)仅在数据同步完成后执行一次(checkIndex),若订正被中断或出现遗漏,索引与文件系统会长期不一致。v3.7.1 引入AutoFixIndex当用户空闲、且存在未订正变更(dirty 标志)时,自动补齐索引订正

3.2 触发条件:三重门槛 + 双检锁

实现位于 kernel/model/index_fix.go,由 cron 每分钟调用:

门槛常量说明
空闲阈值idleFixThreshold = 7 * time.Minute用户连续空闲超过 7 分钟才允许触发
脏标志util.IsIndexFixDirty()存在未订正的变更(dirty)才需要跑
冷却期fixCooldown = 120 * time.Minute上次订正完成后至少间隔 120 分钟才能再次触发
func AutoFixIndex() { defer logging.Recover() if util.IsMobileContainer() { return } // 移动端不执行 if !util.IsIdle(idleFixThreshold) { return } // 空闲不足 7 分钟 if !util.IsIndexFixDirty() { return } // 无脏数据 if !lastFixedAt.IsZero() && time.Since(lastFixedAt) < fixCooldown { return } if !fixIndexMu.TryLock() { return } // 非阻塞,避免与 checkIndex 并发 defer fixIndexMu.Unlock() // double-check:拿到锁后再确认一次确实空闲 if !util.IsIdle(idleFixThreshold) { return } logging.LogInfof("start auto fixing index on idle...") runFixIndexPipeline() }

设计要点:

  • fixIndexMu(kernel/model/index_fix.go)保证checkIndexAutoFixIndex互斥,不会并发跑同一套订正;TryLock非阻塞,若校验正在运行则直接跳过本次,不堆积 goroutine;
  • 加锁后二次确认空闲,避免等待锁期间用户重新开始操作导致订正与用户编辑冲突;
  • 移动端容器(util.IsMobileContainer())明确跳过,与同步后校验行为保持一致(见 kernel/model/index_fix.go)。

3.3 订正流水线:五步 fixIndexPipeline

checkIndexAutoFixIndex共用同一流水线fixIndexPipeline(kernel/model/index_fix.go),每一步之间sql.FlushQueue()冲刷写入队列:

  1. removeDuplicateDatabaseIndex:删除数据库索引中的重复树(按blocks/blocks_fts查重,重复 root 走BatchRemoveTreeQueue);
  2. resetDuplicateBlocksOnFileSys:扫描.sy文件,重置重复块 ID、无效文件名,自动清理遗留的history文件夹(对未解锁的加密笔记本跳过,避免密文被误当损坏数据移走);
  3. fixBlockTreeByFileSys:以文件系统为准订正块树——清理冗余块树、补齐缺失块的索引;
  4. fixDatabaseIndexByBlockTree:对比块树与数据库的updated时间戳(超过 10 分钟偏差即重索引),并清理数据库中已不存在的树;
  5. removeDuplicateDatabaseRefs:删除重复的数据库引用关系。

流水线全程通过状态栏推送进度(util.PushStatusBar,按 1/5 ~ 5/5 步进),完成后util.MarkIndexClean()清除脏标志并记录lastFixedAt,进入冷却期(kernel/model/index_fix.go)。

实战提示:该机制是"自动养护"型设计——升级后无需任何配置,在用户离开键盘 7 分钟以上、且存在索引脏数据时自动触发;用户回来操作会立即被打断吗?不会,因为触发前有双重空闲确认,且订正与用户操作互斥。若想主动检查索引状态,可通过状态栏进度提示观察。


四、增强项逐条拆解:从 URI 到移动端交互

4.1 集市:支持 siyuan://bazaar/{type}/{name}/readme URI

新增 URI 协议:

siyuan://bazaar/{type}/{name}/readme

用于直接打开集市(bazaar)某类包({type}:plugin/widget/icon/theme/template 等)的 README。后端支撑来自 kernel/api/bazaar.go 的getBazaarPackageREADME接口(参数repoURLrepoHashpackageType,并对packageType做了白名单校验),以及 kernel/bazaar/readme.go 的GetBazaarPackageREADME

  • 候选 README 文件名按"当前语言首选 → default → README.md"优先级去重(getReadmeFileCandidates,见 kernel/bazaar/readme.go);
  • 在线拉取失败时依次回退候选文件,全部失败则返回错误拼接信息;
  • 兼容 UTF-16 LE/BE BOM 编码;
  • 通过 Lute 引擎渲染 Markdown 为 HTML,并对代码块注入code-block类名、统一资源链接基准(linkBase)。

4.2 Markdown 导入:解析 audio/video 标签

Improve Markdown import to parse audio/video tags使 Markdown 导入能正确识别<audio><video>标签并转为思源对应的音频/视频块。这延续了 kernel/model/import.go 中"以 Lute 解析并转换节点"的既有导入管线,属于导入能力补齐。

4.3 Electron 与 Windows 行为

  • 禁用混合内容自动升级 HTTPS(PR 17994):Electron 应用中关闭 mixed content 的自动 HTTPS 升级,避免本地 HTTP 资源(如http://127.0.0.1内嵌内容)被强制升级后加载失败;
  • Microsoft Store 版隐藏自动更新选项(Issue 17997):商店版更新由商店托管,隐藏内置自动更新入口,避免双重更新机制冲突。

4.4 编辑器与文档操作

  • 块引用的 padding 区域支持拖拽选择(Issue 15331):从内容左、右、底部留白处开始框选文本,改善选择起始点的容错;
  • 通过块引用新建文档的流程优化(PR 18065):Improve new document creation via block references in the editor
  • 块引用锚文本未变化时跳过引用文档持久化(PR 18066):Skip persisting referencing docs when block ref anchor text is unchanged——锚文本不变就不重复写引用文档,减少无谓的文档写入与索引更新;
  • 固定表头的表格输入不再重置滚动位置(Issue 18035):修复带固定表头表格中编辑时滚动位置跳变的体验问题。

4.5 移动端 / 触屏交互

  • 横向超级块列宽铺满(Issue 14212):移动端横向超级块中列应占满整行宽度;
  • HarmonyOS 与 Android 桌面模式自动弹出屏幕键盘(Issue 18028);
  • 长按并纵向滚动不再误触发多选模式(Issue 18045);
  • iPhone 上设置页从左到右选择文本不再意外关闭页面(Issue 18043);
  • iPhone 上块 gutter 缺失、无法扩展选区(Issue 18055):修复移动端 flashcard 中块标志栏缺失问题;
  • iOS 分享面板缺少思源选项(Issue 18056):修复 iOS 系统分享目标未注册问题;
  • 移动端左侧边栏头部改进(Issue 18003);
  • 将块拖到浮动文档树时 Dock 面板不出现(Issue 18033):修复拖拽到浮动文档树区域时停靠面板无响应。

4.6 输入法 / 键盘

  • macOS 删除不完整拼音时光标跳转其他单元格(Issue 17584):修复中文输入法候选状态删字时编辑器光标错乱的问题。

五、Bugfix 深挖:PDF 搜索与数据库修复

5.1 PDF 文件内文本搜索失效(Issue 17941)

Cannot search text within PDF files修复了 PDF 内文本无法被全局搜索命中的回归问题。思源的全文检索覆盖资源文件(PDF)内容,其实现路径在 kernel/sql/asset_content.go 与 kernel/sql/asset_content_query.go(资源内容入库与查询),底层依赖 PDF 文本抽取(kernel/model/pdf.go)。本次修复使 PDF 文本重新进入索引队列,用户可在搜索面板中检索到 PDF 内的关键词。

5.2 数据库(属性视图)相关修复

  • 分组后无法新增字段(Issue 18004):修复数据库分组视图下新增属性(字段)入口失效;
  • Created time字段过滤不生效(Issue 18017):修复按"创建时间"筛选无效的问题,涉及 kernel/av/av.go 中属性视图过滤逻辑(见 kernel/av/filter.go);
  • 滚动加载相关问题(Issue 18010):修复数据库表格滚动加载的异常;
  • 双端同时打开时数据库视图无法同步(Issue 18027):修复多设备同时在线时数据库视图状态不同步。

5.3 其他修复

  • 启动时偶发卡在 "Finishing boot"(Issue 18008):修复特定场景下启动收尾阶段的挂起,与启动时的索引/队列任务调度相关;
  • 列表块转段落再撤销出现异常(Issue 18012):修复块类型转换后撤销历史状态错乱;
  • iOS 图片不显示(Issue 18013)、部分 Android/iOS 设备索引问题(Issue 18014):移动端资源加载与索引队列的针对性修复;
  • 块图标菜单在窗口左边缘被裁剪(Issue 18024):修复菜单定位溢出;
  • 部分安全漏洞修复(Issue 18032):未公开细节的安全补丁,建议尽快升级。

六、文档更新:工作空间文件系统布局

v3.7.1 新增官方文档Workspace File-System Layout(Issue 18007),系统讲解工作空间目录结构(data/conf/widgets/plugins/themes/storage/temp/等)。仓库内对应文档位于 docs/WORKSPACE.md(含 docs/WORKSPACE.zh-CN.md 中文版),可作为理解思源数据落盘与备份策略的一手资料。


七、升级建议与影响评估

关注点说明
首次启动耗时升级后blocks_fts会重建(external content 模式),首次启动索引重建耗时略增,属预期
磁盘占用FTS 表不再物理存列值,blocksblocks_fts数据去重,长期看数据库体积下降
索引一致性空闲自动订正机制兜底,7 分钟空闲 + dirty 标志 + 120 分钟冷却期自动运行,无需人工干预
移动端同步后校验与空闲订正均跳过移动端容器,移动端依赖同步后的一次性校验
安全包含安全漏洞修复(Issue 18032),建议及时升级至 v3.7.1 及以上版本

总结:v3.7.1 表面是"细节改进",实则暗含一次重要的存储架构调整(FTS5 external content 去重)与索引自愈机制的落地。对于深度用户,理解blocks_fts的 rowid 对齐约束与空闲订正的触发条件,有助于排查搜索/索引类问题;对于开发者,kernel/sql/database.go、kernel/model/index_fix.go 与 kernel/sql/fts_bench_test.go 是学习 SQLite FTS5 external content 实战用法的高质量范本。

【免费下载链接】siyuanAn open-source, privacy-first, self-hosted knowledge workspace where humans and AI agents work together 开源、隐私优先、自托管的知识工作空间,让人与智能体在此协作项目地址: https://gitcode.com/GitHub_Trending/si/siyuan

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 8:20:38

学习报告怎么写?一套四段式复盘模板告别无效努力

我一直有记录每日学习复盘的习惯&#xff0c;11月23日这篇学习报告&#xff0c;是我隔一段时间就想翻出来重看一遍的记录。它没有多宏大&#xff0c;内容就是当天读了哪些资料、动手调试了哪段代码、卡在哪个概念上、第二天打算怎么补&#xff0c;但就是这种看起来朴素的记录&a…

作者头像 李华
网站建设 2026/9/10 8:20:36

Go错误处理实战:从标准库errors到pkg/errors的完整指南

1. 错误处理的核心思路1.1 Go错误模型与其他语言的差异接触Go的人基本第一天就会碰到error这个接口。Go没有异常&#xff08;exception&#xff09;机制&#xff0c;函数出错时通过显式返回error来表示&#xff0c;调用方必须处理或继续向上传播。这个设计在刚开始写的时候会让…

作者头像 李华
网站建设 2026/9/10 8:19:06

Hive性能优化实战:从执行模型到数据倾斜与小文件治理

能让我真正想动笔写 Hive 性能优化的原因&#xff0c;不是又看到一堆参数调优列表&#xff0c;而是我发现很多人把 Hive 调优理解成了“抄参数”&#xff1a;mapred 开大点、reduce 开大点、内存调高点&#xff0c;跑不动就继续加资源。这套路短期看着像那么回事&#xff0c;等…

作者头像 李华
网站建设 2026/9/10 8:15:10

Pandas数据分析全流程:从数据清洗到可视化实战

想聊一个很实际的问题&#xff1a;Pandas在数据分析里到底怎么用&#xff1f;很多朋友学了一堆函数&#xff0c;打开真实数据还是一脸懵。这篇文章我会从数据清洗讲到可视化&#xff0c;用一套完整的流程串起Pandas的核心操作&#xff0c;包括环境配置、类型转换、分组聚合、绘…

作者头像 李华