做数据存储方案时,最容易犯的错误是「看到别人用分布式就跟着上」。这一组笔记覆盖了一条清晰的阅读线:当单库扛不住时怎么做架构演进(分库分表 → 宽表 / HTAP → 分布式存储),再到具体引擎的选型(PostgreSQL、Firebird、Neo4j),最后落到运维底座(binlog、实时监控、多数据源路由)。它们的共性判断是:扩容和选型的难点都不在「怎么拆」,而在拆之后的跨库事务、全局 ID、容量与一致性。
本文先讲架构演进的几条路,再讲具体引擎的适用边界,最后讲运维与监控。建议带着自己业务的主查询路径去读——切分键、宽表列、存储形态都要跟查询路径对齐,而不是跟表字段多少或品牌热度对齐。
一、单库扛不住时怎么拆:分库分表的核心权衡
分库分表是把单库单表承受不住的数据与写压力,按切分规则散落到多个物理库表上。常见切分键是用户 ID、订单号段或时间范围;分库决定连接与资源池,分表决定 SQL 打到哪张物理表。难点不在于怎么拆,而在于拆之后的跨库事务、全局唯一 ID、扩容迁移和复杂查询。一旦按用户维度切分,按商品统计就变成扫全库;反之亦然。因此切分键要跟主查询路径对齐,而不是跟表的字段多少对齐。
先抓住这三点
- 先确认是容量问题还是热点写问题。单表行数过亿、索引胀大才考虑水平拆分;纯读多写少更应上缓存与读写分离。
- 切分键必须出现在几乎所有业务 SQL 的 WHERE 里,否则中间件只能散播。用户维度业务选 user_id 哈希,流水账选时间或账期。
- 预留扩容与迁移路径:双写、按比例切流、旧表只读。全局 ID 用号段或雪花,不要用各库自增。
什么时候用:订单、消息、日志这类持续增长且按用户或时间访问的表,才适合进入分库分表。配置表、字典表、关系复杂的中小规模业务表应尽量保持单库。还能用分区、归档和读写分离解决的,不要过早引入中间件。真正落地时先写路由与数据校验,再谈二阶段提交。
二、宽表与 HTAP:在查询侧与引擎侧消解 JOIN 和 ETL
当查询模式稳定、但反复 JOIN 同一批表时,有两个方向可以消解压力:一种是在查询侧预先打宽表,另一种是在引擎侧用 HTAP 让同一份数据既扛事务又跑分析。
宽表是把多张业务表按查询路径预先打成一张很宽的、冗余的结果表,用空间换查询时的 JOIN。数仓里的 ADS 层、HBase 里的用户画像行、搜索里的商品文档都是宽表思路。它适合读多写少、查询模式稳定的场景。代价是更新复杂:源表一改,宽表要同步,否则出现字段不一致。主键和更新策略要先定,否则后期加列会很痛。
宽表先抓住这三点
- 以查询为中心设计列,而不是把所有表字段都堆进去。用不到的列不要加。
- 明确更新是全量重建还是按主键增量。增量要处理迟到数据和删除。
- 对账:定期用源表抽检宽表字段。发现漂移要能重放。
HTAP 是 Hybrid Transactional/Analytical Processing(混合事务分析处理)。传统架构把 OLTP 放 MySQL、OLAP 放仓库,中间靠 ETL 抽数;HTAP 希望同一份数据既能支持高并发写入,又能跑较重的聚合查询。常见路径是行存加列存的双引擎,或者用复制把事务日志实时同步到分析副本。它不是万能药:事务侧仍需要强一致与短事务,分析侧仍需要扫描友好的字典编码;若把重报表直接打在主库上,仍可能抖动延迟。
什么时候用(宽表 / HTAP):列表页、画像检索、报表这类反复 JOIN 同一批表时,上宽表;事务写入路径仍用范式表,实时宽表可用 Flink 维表关联,离线用数仓调度,不要在 OLTP 库里无限制加冗余列。HTAP 选型时要看做市延迟能否接受、是否要跨源聚合、以及运维是否接受双引擎的复杂度(TiDB、OceanBase 等都在这条路上做取舍)。更实在的问题是:哪些报表必须实时,哪些仍可以留给离线仓库。
三、分布式存储系统:对象、文件、块三种形态怎么选
分布式存储系统要把一份数据放到多台机器上,同时给出可用的读写接口。常见形态是对象存储、分布式文件系统和分布式块存储。它们共享的问题是:如何切分、如何复制、如何在节点失败时仍可读、如何扩容。一致性协议、纠删码和本地化读取是三种不同的代价。业务选型时要先问延迟、容量和是否需要 POSIX,而不是先问品牌。
先抓住这三点
- 副本保证简单可靠但浪费空间;纠删码省空间但修复带宽高。热数据用副本,冷归档用纠删码。
- 元数据服务是瓶颈。小文件海量会把索引打满,要合并或改对象存储语义。
- 故障域要跨机架。同一机架掉电不能让三副本一起没。扩容时数据要能再平衡。
什么时候用:日志、镜像、备份和大数据湖适合对象存储;需要挂载给容器的用分布式文件系统或云盘。不要用分布式存储替代数据库的事务语义。评估时用真实文件大小分布做压测,不要只用大文件吞吐量当指标。
四、关系型引擎:PostgreSQL 与 Firebird 的适用边界
关系型仍然是绝大多数业务的主存储。这里重点看两个差异很大的开源关系库:功能完整的 PostgreSQL,和适合嵌入式/中小规模的 Firebird。
PostgreSQL 是功能完整的开源关系库,支持事务、MVCC、丰富类型和扩展。JSONB、数组、全文检索和窗口函数让很多中小业务不必再外挂专用引擎。复制有流复制和逻辑复制,扩展有 Citus、Timescale、PostGIS。SQL 标准和可预测的执行计划,是它相对 MySQL 常被点名的原因。运维上要盯 vacuum、膨胀和长事务;索引不是越多越好,写多的表要控制二级索引;连接数用连接池卡住,不要让每个微服务直接打满;备份用物理备份加 WAL 归档,定期做恢复演练;升级大版本要看扩展兼容性;查询优化先看 explain analyze,再谈加机器;把约束和部分索引用起来,数据质量会比事后清洗省很多。
Firebird 是开源关系型数据库,从 InterBase 分支而来,适合嵌入式和中小规模服务。它可以嵌入到应用进程里,也可以独立服务器跑。SQL 方言、生成器和存储过程有自己的习惯。国内不少桌面和行业软件仍在用它当本地库。迁移到 PostgreSQL 或 MySQL 时要注意事务隔离、BLOB 和备份工具 gbak。运维上要关心页大小、强制写和锁,而不是用 InnoDB 的经验硬套。
Firebird 先抓住这三点
- 嵌入与服务器模式不要混用同一文件:多进程同时打开嵌入库会损坏,多客户端应走服务模式。
- 备份用官方工具:拷贝正在写的 fdb 文件不等于备份,要用 gbak 或 nbackup。
- 方言与事务:生成器不像自增那样在回滚时回收,设计主键时要清楚。
什么时候用:桌面软件、单机行业系统和需要免安装的本地库可以选 Firebird;高并发互联网业务优先主流分布式或 MySQL 生态。接手老系统时先确认是嵌入还是服务,再谈迁移。PostgreSQL 则适合需要类型丰富、扩展性强、又要稳定执行计划的中小到大型业务。
五、Neo4j 属性图:连接型数据的多跳查询
Neo4j 是属性图数据库,用节点、关系和属性描述连接型数据,查询语言是 Cypher。它擅长多跳关系:组织架构、推荐、知识图谱,而不是大宽表扫描。建模时关系要有方向和类型,索引建在高频查找的属性上。事务与因果一致性要按集群模式理解。和向量库搭配时,Neo4j 管实体关系,向量管相似文本,不要指望它单独扛全文检索。
先抓住这三点
- 先画本体再导入。没有类型约束的图谱会变成难以查询的毛线团。
- 深路径查询要限制跳数和基数。超级节点会把遍历打爆,需要中间汇总或拆分。
- 备份与内存规划按图规模来。全图缓存假设不成立时延迟会陡增。
什么时候用:欺诈关联、权限继承、知识图谱多跳问答适合 Neo4j。日志分析和报表仍用数仓。RAG 场景用它存实体边,文本块仍放向量库。上线前用真实跳数和热点节点压测。
六、运维底座:binlog、实时监控与多数据源路由
架构和引擎定了之后,真正决定能不能睡好觉的是运维底座。这一节把三个偏运维的笔记放一起:MySQL 的 binlog、数据库实时监控方案、以及 Web 多数据源的路由。
MySQL binlog 日志:binlog 是二进制日志,记下对数据的改变事件。主从复制、点后恢复、CDC 都依赖它。格式有 STATEMENT、ROW 和 MIXED。ROW 记录行前后镜像,复制更安全,也是现在的主流选择。事件里有 server_id、事务位点和表映射,从库用位置信息追赶主库。开启 binlog 后要设 expire_logs_days 或自动清理,否则磁盘会被满;行格式下大事务会写出巨大事件,要注意批量更新的影响。解析可用 mysqlbinlog 或 Canal 这类工具;做 CDC 时要保留 GTID,方便断点续传;不要在业务库上直接用重型解析扫全量,应该放到专门的从库或复制专用节点,避免拖慢在线写入。
数据库实时监控方案:监控要盖住可用性、延迟、容量三类信号。可用性看进程是否存活、主从延迟、连接数与锁等待;延迟看慢查询、事务耗时和缓存命中率;容量看磁盘、日志增量、连接池使用率。采集可以用 exporter 打到 Prometheus,慢 SQL 可以从 performance_schema 或审计日志里抽。告警要有阈值和降噪声:CPU 短时间突破不必马上脚本,但从库延迟超过秒级就应该叫人;告警要带上当前顶部 SQL 和主机负载,否则夜里只能猜。日志监控要单独看 error log 与 slow log;变更结构、切换主库之前先看一眼监控基线,避免把变更本身当成故障;把监控面板和跑书绑在一起,出事时才能按步骤处置。
Web 多数据源:指同一应用连接多套数据库,例如读写分离、分库或按业务拆库。实现上要有多个 DataSource、事务不能轻易跨库,以及请求级路由。Spring 里常用 AbstractRoutingDataSource 加线程上下文。坑在于事务管理器绑死了一个库,切换后连接和事务不一致。动态租户库还要考虑连接池数量把数据库打满。
Web 多数据源先抓住这三点
- 路由键放上下文:租户、读写标记进 ThreadLocal,过滤器里设置,请求结束必须清掉。
- 事务不要跨源硬提交:真正跨库用分布式事务或业务补偿,单本地事务绑一个源。
- 连接池按源隔离:一个池打满不影响另一个,监控要带数据源名字。
什么时候用:读写分离、多业务库或按租户分库时需要多数据源。单库应用不要加这层复杂度。接入前先确认事务边界和失败回滚,再写动态路由。
附:引擎选型对照表
| 场景 | 用什么 | 不要用什么 |
|---|---|---|
| 持续增长、按用户/时间访问的表 | 分库分表(user_id 哈希 / 账期切分) | 各库自增主键、过早引入中间件 |
| 反复 JOIN 同一批表的读多写少查询 | 宽表(Flink 维表 / 数仓 ADS) | 在 OLTP 库无限加冗余列 |
| 同一份数据既要高并发写又要重聚合 | HTAP(TiDB / OceanBase 双引擎) | 把重报表直接打在主库 |
| 日志、镜像、备份、数据湖 | 对象存储 | 用分布式存储替代数据库事务 |
| 需要挂载给容器的存储 | 分布式文件系统 / 云盘 | 拷贝文件当备份 |
| 中小业务、类型丰富、要可预测执行计划 | PostgreSQL(JSONB / 扩展) | 每个微服务直连打满连接 |
| 桌面软件、单机行业系统、免安装本地库 | Firebird(嵌入/服务) | InnoDB 经验硬套、多进程开同一嵌入库 |
| 多跳关系:欺诈、权限、知识图谱 | Neo4j(Cypher) | 指望它单独扛全文检索 |
| 跨进程共享状态、低频异步通知 | 多数据源路由 + 消息 | 事务跨源硬提交 |
数据库与分布式存储:从单库到多引擎的选型与落地
https://lautung.com/archives/database-and-distributed-storage
评论