数据库不放进表格的存法

为什么需要

这里的表格,指前几页的。一张表有哪些列、每列放什么、有哪些规则,合起来叫(schema),要先定好;表大了以后再改,可能要把整张表重写一遍。一道菜谱又分在菜谱表、配料表和用户表(每位作者一行,写着名字)几处,取整道菜时,要把几张表里对应的行拼在一起,叫(join);只拼在这一次的结果里,表本身不变。

有的数据只按一个名字来取,比如按编号取整道菜谱。不用「多张表加联接」这一套的数据库有好几类,统称(NoSQL)。这一页只看其中两类。

怎样做

(key-value store)只按名字存取:给出一个名字,叫(key),取回它下面存的内容,叫值(value)。你平时说的键是键盘上的按键,这里只是名字,比如「菜谱:1024」。给出键,数据库直接算出它存在哪里,存得再多也一样快;不知道键,比如要找值里写着番茄的,就只能把所有的值翻一遍,或者自己另建。

(document database)把一整份内容放在一起,比如一道菜谱的菜名、作者、配料都在同一份文档里。每一份的项目可以不同:下图里只有番茄炒蛋那一份多一项小贴士。这里的文档不是 Word 或 PDF 文件。文档里的项目也能查、能建索引。下图把同样四道菜谱存成三种样子,各做两次查询、一次改名,数据量还可以换成 300 万道。

换一种存法:同样四道菜谱,三种存法各做三件事

先看这里先选一件要数据库做的事,看按钮下面三种存法各取出了多少、结论是什么;再把数据量换成 300 万道,比一比取出的数怎样变。

换一种存法的示意图:上面是数据量和三件要数据库做的事(两次查询、一次改名),按钮下面是三种存法的结果,每一种写出结论、取出了多少、怎样找的;再下面是三种存法存着的同样四道菜谱,取出来的那几项加底色,要改的地方另加标记。

数据量
要数据库做的事

还没有选要做的事。按上面的一个按钮,三种存法的结果会写在这里。

数据表

主键之外,配料名也建了索引

菜谱表
编号菜名作者编号
1021罗宋汤7
1022红烧茄子5
1023清炒菠菜7
1024番茄炒蛋7
配料表
菜谱编号配料名
1021番茄
1021牛肉
1022茄子
1023菠菜
1024番茄
1024鸡蛋
1024葱
1024盐
用户表
编号名字
5阿梅
7小林

键值

只按键存取,没有别的索引

  • 菜谱:1021罗宋汤;作者:小林;配料:番茄、牛肉
  • 菜谱:1022红烧茄子;作者:阿梅;配料:茄子
  • 菜谱:1023清炒菠菜;作者:小林;配料:菠菜
  • 菜谱:1024番茄炒蛋;作者:小林;配料:番茄、鸡蛋、葱、盐

数据库只认得键;值里写了什么,要程序取出来以后自己看。

整份文档

编号之外,配料和作者也建了索引

  • 编号
    1021
    菜名
    罗宋汤
    作者
    小林
    配料
    番茄、牛肉
  • 编号
    1022
    菜名
    红烧茄子
    作者
    阿梅
    配料
    茄子
  • 编号
    1023
    菜名
    清炒菠菜
    作者
    小林
    配料
    菠菜
  • 编号
    1024
    菜名
    番茄炒蛋
    作者
    小林
    配料
    番茄、鸡蛋、葱、盐
    小贴士
    出锅前撒上葱花

四道菜谱只列出主要配料,做法都省略了。300 万道、其中 3 万道用到番茄,都是示意;两种数据量下,小林都只写了这 3 道。「取出」数的是从数据库里取出来的行、值或文档,用索引找位置的那几次不算在内。

没有哪一种处处都好。数据表取整道菜要拼三张表,但作者的名字只存一处,改名只改一处,几处数据还能靠和对得上;键值按键取最快,问别的就要翻遍全部;文档一次取回整份,但这里的作者名字抄进了这位作者的每一份,改名要改好几份。各家产品的界限也没有这么分明,比如关系型数据库 PostgreSQL 也能存整份文档;不同的数据库对事务的支持程度也不同。

选存法还要看数据是读得多还是写得多。一段时间里的次数和的次数之比,叫读写比。被读的次数远多于被写的次数,叫(read-heavy);反过来叫(write-heavy)。要一项数据一项数据地看,不是看整个网站。在下图里先猜,再看。

读多还是写多:一项数据被读、被写了几次

先看这里选一项数据,先看这段时间里读者和作者做了什么、各做了几次,猜它是读多写少还是写多读少;猜完再看每件事对这一项数据是读还是写。

这一项数据

菜名、配料和做法,菜谱页上都显示。

这段时间里发生的事
  • 读者打开菜谱页600 次
  • 作者修改菜谱1 次

只数对「菜谱内容」这一项数据的读和写。

你猜

  • 一个点代表一批读取或写入,N 是这一批的次数

读取这一项(这段时间)—
写入这一项(这段时间)—
读写比—

先猜一猜,或者直接看答案。

600、30 和 1 都是示意。从数据库里读出这一项算一次读取,改了它(包括加一)算一次写入;读写比是同一段时间里两者之比。

读多写少的数据,多建几个索引、多放几份只供读取的拷贝都划算:写得少,维护它们就不费事。英文维基百科就是读多写少:按 2026 年 9 月的统计推算,每编辑一次约被浏览 1,200 次(编辑数含机器人)。这里数的是读者浏览和编辑的次数,用来看读和写大致的比例;数据库内部实际读写了多少次,不在这个数里。

写多读少的数据,索引要少而精,宜选写入快、容易分到多台机器上的存法:数据多到一台放不下、请求多到一台忙不过来时,就能分给几台(第 9 章细讲)。网飞(Netflix)的播放记录就是写多读少,写和读约 9 比 1(2018 年)。浏览数这样只按菜谱编号存取、不停加一的数据,正适合键值存储。

代价是什么

代价是要先想清楚数据会被怎样问:键值和文档按「一起用到的就一起存」来组织,问题一变,原来的存法就可能不合适;数据表要先定好表结构,却答得了事先没想到的问题。很多网站几种存法一起用,各放各适合的数据。

回顾:数据库这一章

  • 存在哪里才不会丢:只记在程序内存里的,程序一停下、重新运行就没了;交给数据库,它等写进硬盘才说保存成功,断电重启也还在(第 1 页)。
  • 整理成表:一行是一条记录,一列是一类信息,主键指出是哪一行;一格只放一个值,配料才好按种准确地查,所以另起一张表,一种一行,靠外键连回菜谱(第 1 页)。
  • 很快找到一行:没有索引就逐行翻,300 万行平均翻一半;有索引就对半找,最多 22 次;代价是每新增、删除一行,或改到被索引的那一列,都要顺手改索引(第 2 页)。
  • 改到一半:要改两处的事放进事务,要么全做,要么全不做(第 3 页)。
  • 换一种存法:键值按键取最快,文档一次取回整份,数据表最能灵活地查,各有吃亏的时候;读多写少和写多读少的数据,存法和索引也不同(第 4 页)。

网购下单时,「扣掉库存」和「生成订单」常放进同一个事务:要么一起成功,要么一起取消。

资料来源

以下资料均于 2026-10-23 访问。

  1. ALTER TABLEPostgreSQL 全球开发组(PostgreSQL 18.6 文档),未注明日期
  2. 2.6. Joins Between TablesPostgreSQL 全球开发组(PostgreSQL 18.6 文档),未注明日期
  3. What is a NoSQL Database?AWS,未注明日期
  4. Nosql DefinitionMartin Fowler,2012-01-09
  5. What is a Key Value Database?AWS,未注明日期
  6. GETRedis 文档,未注明日期
  7. Secondary indexingRedis 文档,未注明日期
  8. What Is a Document Database?AWS,未注明日期
  9. DocumentsMongoDB 公司(MongoDB 9.0 手册),未注明日期
  10. IndexesMongoDB 公司(MongoDB 9.0 手册),未注明日期
  11. Data Modeling in MongoDBMongoDB 公司(MongoDB 9.0 手册),未注明日期
  12. Guide to table relationships微软支持,未注明日期
  13. 8.14. JSON TypesPostgreSQL 全球开发组(PostgreSQL 18.6 文档),未注明日期
  14. TransactionsMongoDB 公司(MongoDB 9.0 手册),未注明日期
  15. TransactionsRedis 文档,未注明日期
  16. Wikimedia 统计接口:英文维基百科 2026 年 9 月的用户浏览合计维基媒体基金会(2026-10-23 查询),2026-09
  17. Wikimedia 统计接口:英文维基百科 2026 年 9 月的编辑合计维基媒体基金会(2026-10-23 查询),2026-09
  18. Scaling Time Series Data Storage — Part IKetan Duvedi 等,Netflix 技术博客,2018-01-23