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 适配三件套
- 单进程 LRU 轻量连接池:最多 8 个常驻句柄,请求结束自动清空(
DBFactory,落地 MAX_HANDLES=8);
- 禁止持久连接:禁用
ATTR_PERSISTENT(防句柄泄漏与状态残留);
- 系统层兜底:
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 白皮书开发红线(落地仍强制遵守)
- 禁止前台实时跨桶统计 → 统计全走
_runtime_* / getCategoryStats() 缓存;
- 禁止网页同步更新索引 → 搜索/统计索引走队列异步(sync 模式除外,用户确认的取舍);
- 禁止全局 LIKE 遍历桶文件 → 枚举库文件用显式 glob;
- 禁止长事务、长快照 → 桶写原始 SQL
BEGIN/COMMIT,business 用 beginTransaction/commit,不混用;
- 禁止高峰期 truncate WAL → WAL 回收脚本定时(凌晨)执行;
- 禁止 PDO 持久连接 → DBFactory 禁 ATTR_PERSISTENT;
- 禁止依赖分片自增 ID → 一律 global_id 取号;
- 禁止 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 社区系统做到零运维、低资源、与数据量无关的性能。