速成课 · No. 06
语言和框架来了又走。而你存下的数据、以及你用来存它的那个形状,却能比它们全都活得更久——所以挑一个数据库,就是在选择你的系统如何记忆,以及要改变它的想法有多难。
20 个概念 · 每个都配图示 · 结尾附速查表
数据库是你系统的长期记忆。代码会被重写;而数据、以及你为它选定的那个形状,往往比周围的一切都活得更久。
数据比代码活得久
一栋房子几十年间会重新粉刷、换家具、甚至翻修屋顶——但第一天浇下的地基会留着,因为换掉它意味着要把整栋房子抬起来。
语言会变,框架会被换掉,UI 会被重画十遍。数据和它的结构通常留着,因为在一个装满真实记录的运行中系统上去改它们,既慢又险。所以数据库是第一件要认真想清楚的事,也是最不能仓促的事。
数据库回答两个问题
挑一个存东西的地方,要问两件事:这些东西是什么形状——是书架上的书,还是挂钩上奇形怪状的工具?——以及它们必须多安全——是一个保险库,还是一个顺手的抽屉?
每一种存储都由两件事定义。第一,它的数据模型:它如何塑造数据——刚性的表、灵活的文档、一张由链接构成的图。第二,它的保证:严格的正确性,还是用更松的承诺换来速度与可用性。几乎每一次数据库选型,都是对这两点的一个不同回答——而这门课其余的部分,就是那份菜单。
Access pattern——按你怎么读和写来挑
一个文件柜、一个卡片目录、一条传送带,全都是「存储」——但你按自己将怎么取东西来挑,而不是按哪个看上去最新。
没有最好的数据库。一个对快速键查找堪称完美的存储,用在深层关系查询上就错了;一个为巨量写入而生的存储,用在严格的银行事务上就错了。对的问题是你的访问模式(access pattern):你读什么,你写什么,多频繁,以及当事情出错时它必须怎么表现。
地图:六组共 20 个概念
一张地铁线路图:先看到整条线路,再一站一站地坐过去。
这门课的概念分成六组:relational 核心、NoSQL 家族、专用存储、保证、扩展技术,以及如何把它们全部组合起来。
下面每个概念都得到同样的对待:它的英文名称(你去查文档时要搜的那个词)、一幅生活中的画面、一幅图示,以及一张卡片,字段有:问题、思路、例子、代价、何时使用 和 关联。结尾有一页纸的速查表在等着你。
代码容易改。数据不容易。挑这个存储,要像挑一个你将与之相处多年的决定——因为你真的会。
对大多数系统而言,默认就是 relational 数据库——而它配得上这个默认。从这里起步,只在有理由时才离开。
Relational Database——表、行与关系
一张做对了的电子表格:每个工作表是一类东西(客户、订单),每一行是一条记录,而订单指回它所属的那个客户。
这就是关系型数据库(relational database)。它把数据存在带固定 schema 的表里,并通过引用把表连起来——一条订单行存着它所属客户的 id。这个引用叫做外键(foreign key)。schema 是一份关于形状的承诺:每条订单都有它该有的列,在进来的路上就被校验。先付出结构,换取日后的安稳。
- 问题
- 数据有结构、有关系,而你会对它提出很多问题。
- 思路
- 带固定 schema 的表;表与表通过外键相连。
- 例子
Postgres、MySQL:表
customers和orders,每个订单都有一个customer_id。- 代价
- schema 要事先想清楚;在运行中的数据上改它很费事。
- 何时使用
- 几乎总是,作为起点。 只在有理由时才离开。
- 关联
- 说 SQL,提供 Transactions 和 ACID。
SQL——说出你要什么,而不是怎么拿到
你对图书管理员说「1950 年以后出版的、法国作家的每一本小说,按年份排序」——他自己想办法去找。你描述的是结果,而不是搜索过程。
SQL(Structured Query Language,结构化查询语言)是一种声明式查询语言:你说出想要的答案,数据库自己规划怎么取到它,包括把表连接起来(JOIN)。这个规划由查询规划器(query planner)制定。SELECT ... JOIN ... WHERE ... 能回答你在设计时从没预料到的问题——这是关系模型一项悄无声息的超能力。
- 问题
- 你需要向数据提问,而不必为每次搜索手写代码。
- 思路
描述结果;由数据库挑选执行计划(索引、连接、排序)。
- 例子
SELECT title FROM books JOIN authors ... WHERE country = 'FR' AND year > 1950 ORDER BY year。- 代价
复杂的查询要仔细读,还要检查数据库实际是怎么执行它们的。
- 何时使用
- 凡是有关系型数据库的地方——它是这里的母语。
- 关联
- Relational Database 的语言;靠 Index 变快。
Transactions——要么全做,要么全不做
一笔转账是两次写入——这边减、那边加——但只有一个意图。要么两个都发生,要么都不发生;转到一半是一场灾难。
这就是事务(transaction):一组更改,要么完整地提交(commit),要么完整地回滚(rollback),与其他事务相互隔离,一旦确认就能扛过崩溃。这一整套承诺合称 ACID(第 5 章会详讲)。这就是为什么银行、订单、库存都放在 SQL 里——当正确性比原始速度更重要时,你要的就是这份承诺。
- 问题
- 一个动作是好几次写入;中途崩溃就会留下一堆垃圾。
- 思路
- 把这些写入包在
BEGIN … COMMIT里:全部保存,或者一个都不保存。 - 例子
- 一笔转账:
A − 100和B + 100在同一个事务里。 - 代价
- 损失一些速度,并且跨机器扩展更难。
- 何时使用
- 钱、订单、库存——任何容不得出错数字的地方。
- 关联
- 事务的各项属性就是 ACID。
为什么它是正确的默认选择
一件好用的通用工具:在任何单项活儿上都不是最快的,但在某个活儿明确要求别的工具之前,它就是你伸手去拿的那一件。
有结构的数据、真实的关系、临时起意的查询、强保证——关系型数据库这些都做得好,而且现代 Postgres 还能处理 JSON、全文搜索,甚至向量。每个项目都从这里开始; 只有当一个具体的痛点——规模、形状或访问模式——把你推出去时,才去找别的。
从 Postgres 起步。问题不是「为什么要用 SQL」——而是你有没有一个真正的理由不用它。
有时,刚性的表就是错误的形状。「NoSQL」其实是一个存储家族,每一个都朝不同的方向松动规则,去干不同的活儿。
Document Store——每样东西一个 JSON 文档
一夹填好的表格,每张表格的字段都可以略有不同——没有一个中央模板强迫它们全都一致。
这就是文档存储(document store)。它把记录保存为灵活的、类 JSON 的文档,嵌套、无 schema(schema-less),所以同一个集合(collection)里的两条记录可以不一样。当形状各异或演变很快时非常合适,而一整件东西——一个商品、一份用户资料——就是一个文档,一次读取就能取回。
- 问题
- 记录的形状各异或变化很快;表格碍事。
- 思路
- 每样东西是一个嵌套的 JSON 文档,没有共享的刚性 schema。
- 例子
- MongoDB:商品目录、用户资料。
- 代价
- 保证更少,跨文档查询比 SQL 更难。
- 何时使用
- 一样东西整个读取,而且不同记录有不同字段。
- 关联
- Postgres 也能处理文档(JSONB);常常遵循 BASE 规则。
Key-Value Store——一张巨大的哈希表
一个衣帽寄存处——你递上一个号码牌,拿回的正是你的外套。不用找,不用问,立刻就有。
这就是键值存储(key-value store)——最简单的形状:键进,值出,快得惊人。非常适合缓存、会话、rate-limit(限流)计数器,以及任何凭已知 id 来取的东西。
- 问题
- 你需要凭一个已知的 id 非常快地取回数据。
- 思路
- 键 → 值,像一张巨大的哈希表。
- 例子
- Redis、DynamoDB:
session:42、页面缓存、限流计数器。 - 代价
- 只能按键查找——无法对值做丰富的查询。
- 何时使用
- 缓存、会话、计数器——任何事先就知道 id 的场合。
- 关联
- 在 Polyglot persistence 里,主数据库旁边那个典型的「快速帮手」。
Wide-Column Store——为海量写入而造
一座巨大的仓库,有无尽的相同过道,布局让一千辆叉车可以同时上货而不会互相碰撞。
这就是 wide-column 存储。它按分区键(partition key)把数据分散到许多台机器上,换来海量的写入吞吐和规模,代价是放弃丰富的查询和严格的一致性,好去吞下一股洪流般的数据。强大——但要运维好也需要更多工作。
- 问题
- 写入多到一台 SQL 机器会被压垮。
- 思路
- 按分区键把数据分散到许多节点;并行写入。
- 例子
- Cassandra:事件日志、遥测数据、大规模信息流。
- 代价
- 没有丰富的查询,也没有严格的一致性;更难运维。
- 何时使用
- 写入如洪流,而查询事先已知且简单。
- 关联
- 本质上自带 Sharding;通常遵循 BASE。
Graph Database——当链接才是重点
侦探的线索板——用线连起来的照片。价值不在任何一张照片里,而在谁和谁相连的那张网里。
这就是图数据库(graph database)。它把数据建模成节点和它们之间的边(edge),让关系查询——「喜欢这个的朋友的朋友」「这两个账号之间的路径」——变得自然而快速,而用 SQL 则需要痛苦的多层 join。当连接比记录本身更重要时,就用它。
- 问题
- 主要的问题都关于链接,而在 SQL 里它们会变成好几层的 JOIN。
- 思路
- 节点和边是一等数据;查询就是沿着边走。
- 例子
- Neo4j:社交图谱、推荐、欺诈检测。
- 代价
- 适用面窄;处理普通记录和报表很别扭。
- 何时使用
- 连接比记录更重要。
- 关联
- 当 JOIN 成了痛点时,可替代 Relational Database。
NoSQL 不是「更新版的 SQL」。每种存储都放弃一项保证,去赢得某个具体的东西——要清楚你放弃的是哪一项。
在通用存储之外,还有为某一项工作而造的数据库,专门打造的引擎在那里远远胜过通用的。
Time-Series Database——被时钟盖了戳的数据
心率监护仪的纸带——一条没有尽头的读数长带,每个读数都系着一个时刻,而你几乎总是在问「这两个时间之间发生了什么?」
这就是时序数据库(time-series database)。它为按时间顺序到来、并按时间来查询的数据而调优——指标、传感器读数、价格。它能摄入巨大的数据流,并快速回答「过去一周每分钟的平均值」,还内置对旧数据的过期清理(retention,保留策略)。
- 问题
- 海量的读数流,而每个问题都是「这段时间里发生了什么」。
- 思路
- 按时间存储并建立索引;快速聚合时间窗口。
- 例子
- InfluxDB、TimescaleDB:服务器指标、传感器、价格。
- 代价
- 多一个要运维的存储;不适合普通的业务数据。
- 何时使用
- 所有东西都带时间戳,而时间是主轴。
- 关联
- TimescaleDB 是 Postgres 的扩展——见 Postgres first。
Search Engine——全文与相关性
一个能容忍拼写错误的搜索框,把最佳匹配排在最前,你输入「run」它也能找到「running」——像一个懂你意思的图书管理员。
这就是搜索引擎(search engine)。它建立一个倒排索引(inverted index)——「词 → 文档」——来做快速的全文搜索,带排序、模糊匹配和过滤:这些是 SQL 里的 LIKE '%word%' 做得又慢又差的事。它通常和你的主数据库并排运行,保持同步,支撑搜索栏和日志探查。
- 问题
LIKE '%word%'很慢,不容忍拼写错误,也不排序。- 思路
- 词的倒排索引,外加一个相关性分数。
- 例子
- Elasticsearch、OpenSearch:网站的搜索栏、日志探查。
- 代价
- 必须与主数据库保持同步。
- 何时使用
- 用户按文字搜索,并期待「聪明」的搜索。
- 关联
不是事实来源——只是它上面一面快速的镜片;属于 Polyglot persistence 的一部分。
Vector Database——按含义搜索
不再匹配确切的词,而是问「给我找些感觉和这个一样的东西」——它按相似度返回邻居,而不是按关键词。
这就是向量数据库(vector database)。它存储 embedding——含义的数字指纹——并找出最近邻(nearest neighbours),所以你可以按相似度而不是确切文字来搜索。这是语义搜索和 RAG 背后的引擎,AI 应用靠它取回最相关的片段来作答。
- 问题
- 你需要含义相近的东西,但字面上可能对不上。
- 思路
- 存储 embedding,搜索最近邻。
- 例子
pgvector、Pinecone:查询「refund policy」能找到「returns」和「money back」。
- 代价
- 需要一个 embedding 模型,还要与源数据同步。
- 何时使用
- 语义搜索和 RAG——AI 时代新的必备存储。
- 关联
- pgvector 是 Postgres 的扩展;是数据的一份副本,和 Search Engine 一样。
Object Storage——大文件去的地方
一间自助仓储间——放的不是索引卡片,而是家具:体积庞大的东西,凭标签保管,整件搬出来。
这就是对象存储(object storage)。它不是数据库,但大块的二进制数据——图片、视频、备份、文档——就放在这里,通过 bucket(存储桶)里的键来寻址,并能大规模地廉价提供服务。这个模式几乎是通用的:文件放进对象存储,在数据库里留一行指向它。记录在数据库里,载荷在 bucket 里。
- 问题
- 大文件放进数据库会让它臃肿,而且提供服务成本高。
- 思路
- 文件按键放进 bucket;数据库只留一行带这个键的记录。
- 例子
- S3 及其同类:照片、视频、备份、文档。
- 代价
- 不是数据库:不能对内容做查询,只有「按键 put/get」。
- 何时使用
- 任何大文件。
- 关联
- 形状像 Key-Value Store,只不过装的是庞大的东西。
别把你的主数据库硬掰成搜索引擎或文件服务器。把专门的活儿交给为它而造的存储。
数据库最深的选择不是它的形状——而是出事时它承诺什么。一小碗字母汤,就给这个取舍命了名。
ACID——悲观、正确、谨慎
一位银行柜员,每笔记录都复核两遍,账不平就不往下走——更慢,但从不出错。
ACID 是关系型数据库对事务做出的严格承诺:每个事务要么让数据正确而完整,要么根本不发生。四个字母:Atomicity(原子性,全有或全无)、Consistency(一致性,数据的规则始终成立)、Isolation(隔离性,并发的事务互不干扰)、Durability(持久性,已确认的内容能扛过崩溃)。对钱、订单,以及任何容不得出错数字的地方,这就是你要的取舍。
- 承诺
- 每个事务要么完全正确,要么根本没发生。
- 例子
- 银行转账、下订单、从库存里扣减货物。
- 代价
- 损失一些速度,并且跨机器扩展更难。
- 何时使用
- 出错的数字不可接受。
- 关联
- Transaction 的各项属性;相反的立场是 BASE。
BASE——乐观、可用、最终正确
一家繁忙的商店,即使在更新价签的同时也让大家继续买——数字过一会儿就会跟上,这没关系。
许多 NoSQL 系统采取相反的立场:BASE(Basically Available、Soft state、Eventually consistent)。它们保持快速和可用,并接受数据的各个副本在趋于一致之前可能短暂地不一致。这叫做最终一致性(eventual consistency)。
- 承诺
- 系统总有响应;各副本「最终」会收敛。
- 例子
- 点赞数、信息流、浏览计数器。
- 代价
- 你可能会短暂地看到一个过时的值。
- 何时使用
- 可用性比即时的精确更重要,而且过时一秒钟没有任何损失。
- 关联
- ACID 的对立面;CAP 里选「A」的那一边。
CAP Theorem——网络断开时选哪边
一支被断掉的电话线隔开的团队:每一半都可以各自继续工作(但会渐渐分歧),或者放下工具等重新连上(但就停工了)。两样不可兼得。
CAP 定理说:当一次网络分区(network partition)把你数据库的机器隔开时,你必须在 Consistency(拒绝写入,保持正确)和 Availability(继续服务,冒着不一致的风险)之间选一个——分区期间你不能两者都要。每个分布式数据库都要做这个决定。知道你的存储选了哪个,就知道它在最糟糕的日子里会怎么表现。
- 内容
- 网络分区期间,你不能既正确又可用。
- 例子
- 两个节点失去联系:拒绝这次写入(CP),或者在两边都接受、事后再协调(AP)。
- 代价
- CP——停机;AP——数据过时或冲突。
- 何时考虑
- 一旦你的数据存在于不止一台机器上。
- 关联
- 由 Replication 和 Sharding 引出;AP 那一侧是 BASE。
钱用 ACID,点赞用 BASE。你需要哪种保证,取决于短暂出错的代价有多大。
一小撮技术,能把数据库从一只脆弱的单箱,变成又快又能扛的东西。你会把它们全都遇上。
Index——数据的书末索引
要在一本 900 页的书里找一个词,你不会逐页去读——你翻到书末的索引,直接跳过去。
索引(index)是一个旁路结构,把全表扫描(full table scan)变成直接查找——它是提升读取速度最大的杠杆。 代价是:每个索引都占存储空间,并拖慢写入(每次插入也得更新它)。
- 问题
- 一次查询要读整张表,数据越多就越慢。
- 思路
- 在某一列上建一个有序的旁路结构 → 直接查到那一行。
- 例子
- 在
email上建索引:不用扫描整张表就能找到一个用户。 - 代价
- 磁盘空间,而且每次写入都变慢。
- 何时使用
- 建在你真正用来过滤和排序的列上——不要为了「以防万一」给每一列都建。
- 关联
- 加速 SQL;搜索引擎是针对文本的一种特殊索引。
Replication——留下副本,扛住故障
一份文件放在三栋楼里的三个保险柜中——一个烧毁了,其他的还留着它,而且三个人可以同时读。
复制(replication)把数据的副本保存在多台机器上。所有写入都进入主节点(leader),从节点(副本)(follower, replica)接收一份拷贝并提供读取。如果主节点挂了,一个从节点接管(failover,故障切换)。麻烦在于复制延迟(replication lag):从节点可能落后片刻,所以你刚写入的值也许还没出现在某个副本上。
- 问题
- 一台机器既是单点故障,也是读取能力的唯一上限。
- 思路
- 同样的数据放在多台机器上:主节点写,从节点读。
- 例子
- 带两个只读副本的 Postgres;主节点倒下后,一个副本成为主节点。
- 代价
- 延迟:刚写入的数据可能还没到达副本。
- 何时使用
- 你需要可靠性,或者读取量超过一台机器能承受的。
- 关联
复制的是相同的数据;Sharding 拆分的是不同的数据。两者都会引出 CAP。
Sharding——一只箱子装不下时就拆分
一个塞爆了的文件柜,变成一个柜子放 A–M、另一个柜子放 N–Z。空间多了——但现在你得知道某个名字放在哪个柜子里。
当数据或写入负载超出单台机器时,分片(sharding)把数据拆分到许多台机器上——用户 A–M 放这边,N–Z 放那边。每一部分是一个 shard,你用来拆分的那个字段是分片键(shard key)。它带来近乎无限的规模,但分片键是一个艰难、近乎永久的选择: 选错了,你就会遇到热点(hotspot),或者必须访问每一个 shard 的查询。
- 问题
- 数据或写入流一台机器装不下。
- 思路
- 按一个键把数据拆分到许多台机器上。
- 例子
- 用户 A–M 在 shard 1,N–Z 在 shard 2。
- 代价
- 这个键近乎永久;键选得不好 → 热点,或者要跨每个 shard 的查询。
- 何时使用
- 最后一步:在索引和副本之后。
- 关联
- 内置于 Wide-Column Store;与 Replication 一起会引出 CAP。
Normalization / Denormalization——一个事实放一处,还是为了速度留副本
一份人人都引用的地址总表(没有矛盾),对比把地址的一份拷贝订在每一张订单上(读起来更快,但现在有一堆副本要保持诚实)。
规范化(normalization)让每个事实只存在于恰好一个地方——干净,没有矛盾,是关系型的默认做法。反规范化(denormalization)则故意复制数据,让读取更快、需要的 join 更少。两者都成立;规矩是要有意识地去做,并且始终清楚哪一份是事实来源,哪一份只是快速的副本。(和缓存是同一个教训。)
- 问题
- 同一个事实放在多处会渐渐对不上;但 join 会拖慢读取。
- 思路
- 默认规范化;在读取更重要的地方有意识地反规范化。
- 例子
- 地址放在一张
addresses表里,对比每张订单里各存一份拷贝。 - 代价
- 规范化——更多 JOIN;反规范化——要保持同步的副本。
- 何时使用
- 当可测量的慢读取要求时,再反规范化。
- 关联
- 和 Polyglot persistence 是同一个「事实来源 + 快速副本」的思想。
先加索引,再加服务器;先加副本,再分片。最便宜的扩展,是你不必去运维的那一种。
你不会只挑一个数据库,然后把一切都硬塞进去。真实的系统会用上几个,每个干它最擅长的活儿。
Polyglot Persistence——有意地使用多个存储
厨房里有冰箱、冷冻柜和储物柜——而不是一只箱子被逼着同时干这三件事。各自存放适合它的东西。
这就是多语言持久化(polyglot persistence)模式。成熟的系统通常会用好几个存储:Postgres 作事实来源,Redis 做缓存和会话,一个搜索索引服务搜索栏,也许再加一个向量存储给 AI 功能用,以及对象存储放文件。每个存储做它擅长的事。要守的纪律是始终清楚哪一个握有真相,其余的都是保持同步的快速副本。
- 问题
- 没有哪个数据库能样样第一。
- 思路
- 每项活儿有自己的存储;有一个明确的事实来源。
- 例子
- Postgres + Redis + 一个搜索索引 + 一个向量存储 + S3。
- 代价
- 每个存储都要运维、备份,并保持一致。
- 何时使用
- 当一个真实的痛点需要另一个存储时——而不是「以防万一」。
- 关联
- 从 Postgres first 出发;把这门课里几乎所有存储串在一起。
Postgres First——从 Postgres 起步,有意地逐步超越它
一把瑞士军刀,在你需要整套工具箱之前,能应付惊人多的事情。
这就是 Postgres first 的做法。现代 Postgres 的能力令人惊讶——relational 核心、JSON 文档、全文搜索、向量,甚至一个够用的任务队列。大多数产品应该只从 Postgres 开始,只有当一个真实的、可度量的痛点需要时,才加一个专用存储。每多一个数据库,就多一样要运维、备份和保持一致的东西。最便宜的存储,就是你没加的那个。
- 问题
- 团队在痛点出现之前就加数据库。
- 思路
- 先用一个 Postgres,直到某个可度量的问题要求更多。
- 例子
- 用 JSONB 代替单独的文档存储,用 pgvector 代替单独的向量数据库。
- 代价
- 在非常大的规模下,专用引擎仍然会赢。
- 何时使用
- 几乎任何产品的起步阶段。
- 关联
- 迈向 Polyglot persistence 的第一步——等「暂时」结束之后。
这一切是怎么连起来的
走了很久的路之后的街区地图:现在你看得出哪条街通向哪里。
这门课里的概念不是孤立存在的。关系型数据库是带 ACID 保证的事实来源;索引加速它的读取;搜索引擎和向量数据库是从它喂出来的副本;对象存储存放它的行所指向的文件。当一台机器不够用时,复制和分片就登场了——随之而来的是 CAP 的抉择。NoSQL 家族常常选 BASE——ACID 的反面。
- 数据是什么形状——表、文档、键值,还是图?
- 我将怎样查询它——按 id、按关系、按文本、按相似度,还是按时间?
- 我需要什么保证——严格的 ACID,还是最终一致就够了?
- 读写的比例如何,以及大致的规模?
- Postgres 是不是已经能做这件事,在我再加一个存储之前?
- 哪个存储握有事实来源,哪些只是副本?
- 你用
LIKE '%...%'做搜索,而不是用搜索引擎。 - 一个 键值存储,而你却老是需要按值去查询。
- 两个人维护的产品用了五个数据库。
- 你选 MongoDB 是为了避开 schema,结果还是手工重建了校验。
- 没有明确的事实来源——两个存储都自称是权威。
- 你最常跑的那个查询,正是这个存储最快的。
- 保证与利害相匹配——钱要严格,点赞可放松。
- 你能说出每个重要事实的事实来源。
- 你加每个存储都是为了解决一个真实、切身的痛点,而不是假想的。
- 你能在几分钟内向新工程师讲清楚数据模型。
速查表:全部 20 个概念
关系型核心
- Relational DatabaseRDBMS
带固定 schema 的表,由外键连接。默认之选。
- SQL查询语言
描述你要什么;由数据库决定怎么取。
- Transaction全有或全无
多次写入作为一个单元:要么全部 COMMIT,要么全部 ROLLBACK。
NoSQL 家族
- Document Store文档数据库
灵活的 JSON 文档;一样东西整个读取。
- Key-Value Store键 → 值
键 → 值,立刻拿到。缓存、会话、计数器。
- Wide-Column Store列族存储
把数据分散到许多机器上,吞下写入洪流。
- Graph Database图存储
节点和边;关系查询,没有痛苦的 JOIN。
专用存储
- Time-Series DatabaseTSDB
按时间的读数;按窗口查询,旧数据过期。
- Search Engine全文搜索
带排序和拼写容错的全文搜索。是副本,不是真相。
- Vector Database向量存储
Embedding 与按含义找最近邻的搜索。RAG 的基础。
- Object StorageS3
大文件按键放进 bucket;数据库只留一个指针。
保证
- ACID严格的承诺
一个事务要么完全正确,要么没发生。用于钱。
- BASE最终一致性
始终可用,副本最终收敛。用于点赞。
- CAP Theorem在 C 与 A 间选一个
网络分区期间——要么正确性,要么可用性。
规模
- IndexB-tree index
一个让读取变快的指针;代价是空间和更慢的写入。
- Replication主从
相同的数据放在多台机器上:可靠性与读取。存在延迟。
- Sharding水平分区
按键把不同的数据放在不同的机器上。这个键近乎永久。
- Normalization / Denormalization一处存放 vs 副本
一个事实放在一处——或者为了速度有意留下副本。
选择
- Polyglot Persistence多个存储
每项活儿有自己的存储,只有一个事实来源。
- Postgres First从 Postgres 开始
先用一个 Postgres,直到真实的痛点需要更多。
最好的数据库决定,通常是「暂时用 Postgres」——外加恰好知道「暂时」何时结束的智慧。