SplitDB — 基于SQLite的分片数据库介绍

技术分享 flinthub 2026-08-31 16:29 11 0
SplitDB — 基于SQLite的分片数据库介绍

SplitDB v2.3 是 FlintHub(原 SoSite)底层数据引擎的架构定稿方案:基于原生 SQLite 构建的多分片存储系统。 核心思路:用时序分区 + 哈希分桶解决 SQLite 单文件锁竞争;分片作为唯一数据源,索引 / 搜索 / 统计为异步二级视图。 无需 MySQL、Redis、消息队列 —— 一个 PHP 进程 + 若干 SQLite 文件即可承载一个社区。 本文档依据《Splitdb V2.3 白皮书.txt》整理,并结合 SplitDB 在 FlintHub 中的实际落地情况与实测数据,最后给出与 MySQL 在小数据量 / 大数据量下的对比。


一、SplitDB 是什么

1.1 定位

SplitDB 面向中小型私有化社区、独立站长设计,精准适配低资源服务器(1 核 512M ~ 1 核 1G)

  • DAU 0~8000:稳定舒适运行;
  • DAU 8000~15000:限流可控稳定运行;
  • 不适用:超高并发刷屏、电商秒杀、复杂多表 JOIN 统计、大型门户(白皮书明示边界,预留了后期平滑切换 MySQL 驱动的适配器)。

1.2 三大核心设计理念(白皮书原文原则)

# 原则 含义
1 Bucket 分片 = 唯一真实数据源 永不丢失、永不错乱、可全量重建所有索引
2 全局索引、搜索、统计 = 二级缓存视图 最终一致性,允许异步延迟
3 读无限扩容、写可控分片、复杂查询全部预计算 / 异步化 把"查询"降级为"拼装缓存"

一句话概括:分片管写入(真相源),索引管读取(二级视图),队列管异步(最终一致),缓存管性能(读降级为拼缓存)。

1.3 与 MySQL 的本质差异(先给结论)

维度 MySQL SplitDB
进程模型 独立数据库服务进程,常驻内存 无独立进程,嵌入式,随 PHP 请求即用即走
资源占用 安装/配置/调优成本高,1 核 512M 紧张 1 核 512M 流畅,零守护进程
数据组织 单库单表(InnoDB B+Tree) 季度时序分区 + 32 桶哈希分片(多文件分散锁竞争)
锁竞争 单表行锁/间隙锁,高并发热点行争锁 分桶后每桶独立 SQLite 文件,WAL + 分片双保险
一致性 强一致为主 最终一致性(异步二级视图,业务需接纳短暂延迟)
扩展 主从/分库分表,运维复杂 季度升级桶数(32→64→128),历史数据零迁移
部署 需安装数据库、建账号、配权限 复制文件 + 配置 data/ 目录即可

二、架构总览

2.1 数据目录(白皮书定稿)

data/
├── meta/                          # 元数据层
│   ├── main_index.sqlite          # 帖子主索引(列表、分页、路由)
│   ├── search_index.sqlite        # 全文检索库(落地改为季度分文件,见 3.2)
│   ├── stats_cache.sqlite         # 预计算全站统计(落地并入 business.sqlite 的 _runtime_*)
│   ├── task_queue/queue_{0..2}.sqlite   # 多队列(3 个打散锁竞争)
│   └── global_id.sqlite           # 全局 ID 生成器(写单点,高并发可扩展 ID 预分配)
├── bucket/
│   ├── active/{2026Q3}/{0..31}.sqlite   # 真相源分片:季度 × 32 桶
│   └── archive/                   # 18 个月前冷数据归档
├── extern/{年}/{季}/{桶}/{id}.txt # 正文外置(落地:存量合并 .bin/.idx 偏移索引)
├── runtime/  lock/  log/

2.2 分区与路由规则(ShardRouter)

  • 一级分区:季度时序分区YYYYQn):新数据永远写当前季度,超 18 个月归档至 archive/
  • 二级分区:哈希分桶(默认 32 桶):写入路由 = ID % 桶数量
  • 重大优化读取永远以 main_index.bucket_path 为准,不再二次哈希 —— 未来季度可直接升级 32 → 64 → 128 桶,历史数据零迁移、零失效。

2.3 统一强制 PRAGMA(每个连接固定 6 条)

PRAGMA busy_timeout = 5000;        -- 写锁等待上限,防 SQLITE_BUSY 崩溃
PRAGMA journal_mode = WAL;         -- 读不阻塞写、写不阻塞读
PRAGMA synchronous = NORMAL;       -- WAL 下性能与安全的平衡
PRAGMA cache_size = -20000;        -- 20MB 页缓存
PRAGMA temp_store = MEMORY;        -- 临时表走内存
PRAGMA foreign_keys = OFF;         -- 分片架构不依赖跨文件外键

2.4 写入 / 读取流程

写入 = global_id 原子取号 → 季度 + ID2 哈希 → LRU 取桶连接 → 写 Bucket + extern 正文
     → 随机入 3 队列之一(打散锁竞争)→ 立即返回 topic_id + bucket_path
读取 = 列表/首页/分页走 main_index → 详情按 bucket_path 读单桶 → 搜索走 search_index

2.5 PHP-FPM 适配三件套

  1. 单进程 LRU 轻量连接池:最多 8 个常驻句柄,请求结束自动清空(DBFactory,落地 MAX_HANDLES=8);
  2. 禁止持久连接:禁用 ATTR_PERSISTENT(防句柄泄漏与状态残留);
  3. 系统层兜底ulimit -n 65535 / php-fpm rlimit_files = 65535

三、SplitDB 在 FlintHub 的落地情况

白皮书是设计蓝图,FlintHub 1.0 是它的完整实现。落地过程中针对 PHP 8.0.2(SQLite 3.33.0,无 RETURNING 支持)与真实业务做了关键适配:

3.1 落地组件清单(app/SplitDB/)

组件 对应白皮书模块 落地亮点 / 适配
DBFactory 模块一(连接池) LRU ≤ 8 句柄 + 统一 6 条 PRAGMA + 禁持久连接,逐条落实
IDGenerator 模块二(全局 ID) 白皮书用 UPDATE...RETURNING(需 SQLite ≥3.35),落地改用 BEGIN IMMEDIATE 写锁事务(兼容 3.33),原子性等价
ExternStorage 模块三(原子写外置) tmp+rename 原子写落实;新增存量 .bin/.idx 偏移索引(35 万小文件合并,fseek 定位);invalidateArchiveEntry() 修复虚拟主机编辑不生效
QueueConsumer 模块四(CAS 乐观锁) CAS 抢占 / 超时恢复 / 重试上限 3 次全部落实;新增 3 队列打散锁竞争 + sync/cron/cli 三模式
ViewCounter 模块五(APCu 计数) APCu 内存计数 + shutdown 批量落库;无 APCu 静默降级为直写 UPDATE(不崩站) —— 白皮书风险提示的落地解法
ShardRouter 分区路由 季度 + ID2 哈希;读取永远以 main_index.bucket_path 为准(不二次哈希)
Schema 建表/目录 幂等 bootstrap:目录树 + 8 库 + 22 表 + 默认种子(smoke_install 30/30)
SearchIndexStore search_index 白皮书 FTS5 → 落地保留自研 bigram 倒排(用户决策),并季度分文件物理隔离

3.2 与白皮书的关键差异(落地决策)

白皮书设计 FlintHub 落地 原因
UPDATE...RETURNING 取号 BEGIN IMMEDIATE 写锁事务 PHP 8.0.2 内置 SQLite 3.33 不支持 RETURNING(需 ≥3.35)
FTS5 全文检索 自研 bigram 倒排索引 用户决策保留原有搜索方案(中文分词可控),表平移至 SQLite
单文件 search_index 季度分文件 search_{YYYYQn}.sqlite(4 文件 235 万行) P21 瘦身:business 库 306.7MB → 19MB,索引写入不阻塞业务库
extern 单 .txt 存量合并 .bin/.idx 偏移索引(24,651 组,35.1 万条) 35 万小文件 inode 压力 → 4 字节长度头 + fseek 定位,读取 ~0.7ms
stats_cache 独立库 并入 business 库 _runtime_* KV + Category::getCategoryStats() 永久缓存 简化库数量,统计查询降级为读缓存
60s TTL 缓存 永久化缓存(仅发帖/回帖失效)+ 入口前置缓存 P23/P24 实测:游客命中 ~2.3ms

3.3 落地后的真实性能(19.2 万帖 / 5 万用户实测)

指标 优化前 优化后
首页(未命中缓存完整渲染) 0.11s 20ms(逐查询剖析全部亚毫秒7ms)
首页(入口前置缓存命中) ~2.3ms(keep-alive)
统计 GROUP BY(19.2 万行全表) ~99ms 2.14ms(永久缓存命中)
users 排序(后台首页) 26.5ms(无索引) 0.05ms(idx_users_created)
在线人数统计(5 万行模拟) 5.94ms 0.10ms(索引 + 30s 缓存)
深翻页第 12000 页 188ms(OFFSET) 0.36ms(keyset 行值比较)
数据量相关性 随数据量线性恶化 与数据量无关(300 帖 vs 19.2 万帖 warm 渲染均 7-20ms)

★ 最有说服力的一条:300 帖小数据集与 19.2 万帖真实库的 warm 渲染耗时相同(均 7-20ms)——SplitDB 的"读无限扩容"理念在真实数据下得到验证,性能不随数据量退化。


四、与 MySQL 的对比

对比基线:SoSite 5.0(MySQL 版)历史表现 vs FlintHub 1.0(SplitDB 版)落地实测;MySQL 数据来自项目历史与交接文档记录,SplitDB 数据来自真实环境实测。

4.1 小数据量(百帖 ~ 数千帖时代)

MySQL(SoSite 5.0 时代) SplitDB(FlintHub 1.0)
首页渲染 ~0.008-0.01s(200 帖时代实测) ~7-20ms(warm,300 帖对照实验)
写路径 INSERT 单表 + 事务 global_id 取号 + 哈希 + 桶写 + extern,前端先返回(队列异步)
查询 单表 SELECT,无分片 main_index 索引行 + bucket_path 定位,一样亚毫秒
部署成本 需安装 MySQL 服务、建库建账号、配 InnoDB 参数 零守护进程,data/ 目录可写即可
首启成本 内存常驻,低配服务器吃紧 嵌入式即用即走,1 核 512M 流畅
结论 小数据量下两者都很快 小数据量下无明显差异,但 SplitDB 部署/运维成本远低

4.2 大数据量(19.2 万帖 / 5 万用户 / 235 万搜索行)

MySQL(SoSite 5.0 时代) SplitDB(FlintHub 1.0 实测)
首页 0.11s(统计 GROUP BY 全表扫描 ~99ms + COUNT ~46ms + users 排序 ~26ms) ~2.3ms 缓存命中 / ~20ms 完整渲染;统计 2.14ms
列表深翻页 线性 OFFSET(第 12000 页 ~188ms) keyset 游标 0.36ms
搜索 单表倒排索引 季度分文件索引(235 万行,写入不阻塞业务)
数据库体积 business 库膨胀至 306.7MB(含搜索索引) 正文外置 extern + 索引季度分文件后 business 仅 19MB
热点写竞争 单表热点行争锁 32 桶分片 + WAL,写锁竞争面收敛到单桶
统计查询 前台实时 GROUP BY(随数据量恶化) 永久化缓存(发帖/回帖失效),读降级为拼缓存
维护 需要索引调优/分库分表规划 桶数平滑升级(32→64→128),历史零迁移
结论 大数据量下开始出现全表扫描、锁竞争、体积膨胀 性能与数据量基本无关,缓存体系兜底

4.3 综合对比表

维度 MySQL SplitDB 结论
性能(小数据) ★★★★★ ★★★★★ 打平
性能(大数据) ★★★(需调优) ★★★★★(实测) SplitDB 胜
部署复杂度 ★★(服务+配置) ★★★★★(复制即用) SplitDB 胜
资源占用 ★★(常驻进程) ★★★★★(嵌入式) SplitDB 胜
一致性 ★★★★★(强一致) ★★★(最终一致) MySQL 胜(业务需接纳异步延迟)
复杂 SQL ★★★★★(JOIN/聚合强) ★★★(跨桶查询受限) MySQL 胜(白皮书明确边界)
扩展性 分库分表成本高 桶数平滑升级零迁移 SplitDB 胜
超高峰值 ★★★★ ★★(写单点 global_id) MySQL 胜(白皮书明确边界)

4.4 选型结论(白皮书边界 + 落地验证)

  • 选 SplitDB 当:中小型私有化社区、独立站长、低配服务器(1 核 512M)、DAU ≤ 8000 舒适 / ≤ 15000 限流可控、想要"零运维 + 纯 SQLite"的极简部署;
  • 选 MySQL 当:超高并发刷屏、电商秒杀、复杂多表 JOIN 统计分析、大型门户(超 SplitDB 边界)。
  • 白皮书为极端场景预留了适配器模式(后期可平滑切换 MySQL 驱动),FlintHub 的 Model 层 API 已按此设计,切换成本可控。

五、开发红线与注意事项(沉淀)

5.1 白皮书开发红线(落地仍强制遵守)

  1. 禁止前台实时跨桶统计 → 统计全走 _runtime_* / getCategoryStats() 缓存;
  2. 禁止网页同步更新索引 → 搜索/统计索引走队列异步(sync 模式除外,用户确认的取舍);
  3. 禁止全局 LIKE 遍历桶文件 → 枚举库文件用显式 glob;
  4. 禁止长事务、长快照 → 桶写原始 SQL BEGIN/COMMIT,business 用 beginTransaction/commit,不混用;
  5. 禁止高峰期 truncate WAL → WAL 回收脚本定时(凌晨)执行;
  6. 禁止 PDO 持久连接 → DBFactory 禁 ATTR_PERSISTENT;
  7. 禁止依赖分片自增 ID → 一律 global_id 取号;
  8. 禁止 HTTP 线程实时更新浏览量 → APCu 计数 + shutdown 批量 flush(无 APCu 降级直写)。

5.2 白皮书风险提示的落地处置

白皮书风险 落地处置
global_id 为写入单点 当前规模无瓶颈;已预留 ID 预分配机制扩展点
异步最终一致性 三种队列模式(sync 实时 / cron 定时 / cli 常驻),sync 模式数据 100% 实时
浏览计数依赖 APCu ViewCounter 无 APCu 静默降级直写,不崩站
Windows 原子写兼容 tmp+rename 在 Windows 实测通过;虚拟主机编辑不生效问题已用 invalidateArchiveEntry() 修复
部署最低要求 PHP ≥ 7.4(实测 8.0.2)、PDO_SQLITE 内置、data/ 可写

5.3 运维命令速查

# 语法检查
php -l app/SplitDB/DBFactory.php

# 重建搜索索引(二级视图可从分片全量重建)
php cli/rebuild_search.php

# 队列消费(--once 供 cron)
php cli/worker.php 0 --once

# 性能探针(纯 PHP 读缓存计时)
php cli/probe_page_cache.php --quiet

# WAL 回收(对应白皮书 splitdb_wal_reclaim.sh,凌晨执行)
# 数据库信息/备份/索引管理:后台 → 数据库维护

本文档完。SplitDB —— 分片之光:用纯 SQLite 分片引擎,把 PHP 社区系统做到零运维、低资源、与数据量无关的性能。

×