主题
6.1 数据存储演进
本节目标:理解数据存储方式的演进路径,知道什么时候该用数据库。
小明的豆瓣电影梦
从这一节开始,我们跟着一个叫小明的独立开发者,走完从数据库到产品上线的完整旅程。他和你一样,用 Claude Code 写代码,踩的坑和你会踩的差不多。
小明是个重度电影迷。每周至少看三部电影,从院线大片到小众文艺片,从好莱坞到日韩欧洲,来者不拒。他有个习惯:每看完一部电影,就在手机备忘录里写几句感想。
时间一长,备忘录里攒了几百条零散的观影记录,找起来极其痛苦。他想搜"去年看过的科幻片里哪部最好",只能一条一条往上翻。
于是小明决定做一个"个人豆瓣"——一个属于自己的电影数据库,能记录每一部看过的电影,打分、写短评、贴标签,还能按各种维度筛选和统计。
一开始他觉得很简单:开个 Excel 不就行了?
于是他打开 Excel,建了一张表:
| 电影名 | 导演 | 评分 | 标签 | 年份 | 短评 |
|---|---|---|---|---|---|
| 流浪地球 | 郭帆 | 7.9 | 科幻 | 2019 | 特效炸裂 |
| 哪吒之魔童降世 | 饺子 | 8.4 | 动画 | 2019 | 笑中带泪 |
| 霸王别姬 | 陈凯歌 | 9.6 | 剧情 | 1993 | 永远的经典 |
前 20 部电影,一切顺利。列也不多,管理起来很轻松。
但小明是个重度影迷,很快就记录了 200 多部。问题开始一个接一个地冒出来——而且每个问题都比上一个更让人头疼。
第一阶段:CSV / Excel — 能用,但很快就不够
Excel 和 CSV 是大多数人接触数据管理的起点。CSV(Comma-Separated Values,逗号分隔值)本质上就是纯文本版的 Excel,每行一条记录,逗号隔开字段。你用记事本就能打开它,任何编程语言都能轻松读写:
csv
电影名,导演,评分,标签,年份
流浪地球,郭帆,7.9,科幻,2019
哪吒之魔童降世,饺子,8.4,动画,2019
霸王别姬,陈凯歌,9.6,剧情,1993小明遇到的问题:
问题一:一部电影多个标签怎么办?
《流浪地球》既是"科幻"又是"灾难"还是"国产"。在 CSV 里,标签列只能写成 科幻/灾难/国产,用斜杠拼在一起。想查"所有科幻片"?得用字符串匹配,又慢又容易出错——"科幻片"和"科幻"匹配不上,"硬科幻"也匹配不上。
这个问题的本质是:CSV 的每个格子只能放一个值。当一个字段需要存多个值时(多个标签、多个演员),CSV 就力不从心了。你只能把多个值硬塞进一个格子里,然后在查询时用各种字符串技巧去拆分——这既脆弱又低效。
问题二:数据不一致
小明有时候写"郭帆",有时候写"郭帆导演",有时候手滑写成"郭凡"。200 条数据里,同一个导演可能有三四种写法。Excel 不会提醒你,错了就是错了。
等到小明想统计"郭帆导演的作品平均分"时,他发现只统计到了一部——因为另外两部的导演名字写错了。这种错误在数据量小的时候不明显,数据一多就成了噩梦。你甚至不知道有多少数据是错的,因为没有任何机制帮你检查。
问题三:关联数据放不下
小明想加一个功能:记录每部电影的演员表。一部电影有十几个演员,一个演员演过几十部电影。这种"多对多"关系,一张 Excel 表根本放不下。
硬塞的话,要么一行变得超级长(把所有演员名字用逗号拼在一起),要么得建好几张表然后手动对照——"这个演员 ID 对应哪个人来着?"每次查询都要在多张表之间来回跳,纯靠人脑关联,效率极低还容易出错。
问题四:多人协作是灾难
小明想让朋友一起维护这个片单。两个人同时编辑同一个 Excel 文件?要么覆盖对方的修改,要么产生冲突文件。Google Sheets 虽然能多人同时编辑,但它本质上还是电子表格,前面三个问题一个都没解决。
CSV/Excel 适合什么?一次性的数据分析、简单的配置清单、几十行的小数据。一旦数据量上去、关系变复杂、需要多人协作,就该换方案了。
第二阶段:JSON 文件 — 程序员的本能选择
小明学了点编程后,发现了 JSON 这个格式。JSON(JavaScript Object Notation)是 Web 开发中最常见的数据格式——你在前面章节接触过的 package.json、tsconfig.json 都是它。
JSON 比 CSV 灵活得多,最大的优势是支持嵌套结构和数组。小明之前在 CSV 里搞不定的"一部电影多个标签"问题,在 JSON 里轻松解决:
json
{
"movies": [
{
"id": 1,
"title": "流浪地球",
"director": "郭帆",
"rating": 7.9,
"tags": ["科幻", "灾难", "国产"],
"review": "特效炸裂",
"actors": ["吴京", "屈楚萧", "李光洁"]
}
]
}标签和演员可以用数组了,嵌套结构也能表达了。小明很开心,觉得终于找到了完美方案。
但当他把应用做成 Web 版、开始有用户访问时,新的问题又来了:
问题一:查询效率低
想查"所有评分 8 分以上的科幻片",程序需要把整个 JSON 文件读进内存,然后一条一条遍历,检查每条记录的 rating 和 tags。数据量小的时候感觉不到,但当小明的电影库涨到几万条时,每次查询都要遍历整个文件,页面加载明显变慢了。
数据库不会有这个问题——它有索引机制,可以直接跳到符合条件的数据,不需要逐条扫描。这就像在一本书里找某个关键词:没有目录就只能从头翻到尾,有了目录就能直接翻到对应页码。
问题二:没有数据约束
JSON 文件不会阻止你写入错误数据。评分写成 "很高" 而不是数字?演员 ID 指向一个不存在的人?文件不会报错,数据就这么悄悄脏了。你可能几个月后才发现某些数据是错的,但那时候已经不知道是什么时候、被谁写错的了。
数据库有约束机制——你可以定义"评分必须是 0-10 之间的数字"、"演员 ID 必须指向一个真实存在的演员",违反规则的数据根本写不进去。
问题三:并发写入会丢数据
两个请求同时读取 JSON 文件、各自修改、各自写回——后写的那个会覆盖先写的修改。这在 Web 应用里是致命的。想象两个用户同时给不同的电影打分,结果只有一个人的打分被保存了,另一个人的修改凭空消失。
这个问题叫竞态条件(Race Condition)——你在电商大促时抢过最后一件库存吗?两个人同时点"立即购买",谁能下单成功取决于谁的请求先到服务器,结果不可预测。这是并发编程中最经典的 bug 之一。数据库通过事务和锁机制从根本上解决了这个问题——事务保证"一组操作要么全成功要么全撤销",锁保证"同一时刻只有一个人能改这条数据"。
问题四:关联查询很痛苦
想查"吴京演过的所有电影的平均分",需要遍历所有电影、检查演员数组里有没有"吴京"、收集评分、算平均值。代码写起来又长又慢。如果数据分散在多个 JSON 文件里(电影一个文件、演员一个文件),关联起来更麻烦——你得自己写代码把两个文件的数据"拼"在一起。
数据库有专门的 JOIN 操作来做这件事,一条语句就能把多张表的数据关联起来,而且数据库内部会自动优化查询路径,比你手写的遍历代码快几个数量级。
JSON 适合什么?应用的配置文件(
package.json、tsconfig.json)、API 的数据传输格式、少量的静态数据。但作为业务数据的持久化存储,它撑不住。
第三阶段:数据库 — 专业的事交给专业的工具
小明在 CSV 和 JSON 上踩了一圈坑之后,终于决定用数据库。他选了 PostgreSQL(原因在 6.0 节已经介绍过),把数据拆成了几张表:
movies表:存电影基本信息(片名、年份、评分、简介)directors表:存导演信息(姓名、国籍)actors表:存演员信息(姓名、国籍)movie_actors表:记录哪个演员演了哪部电影(中间表,解决多对多关系)tags表:存标签(科幻、动作、文艺……)movie_tags表:记录哪部电影有哪些标签(又一个中间表)
你可能会问:为什么要拆成这么多张表?直接一张大表不行吗?
不行。一张大表意味着大量的数据冗余。比如"吴京"这个演员出现在 20 部电影里,如果用一张表,"吴京"的名字、国籍等信息就要重复存 20 遍。改一下他的信息,就要改 20 个地方,漏改一个就是数据不一致。拆成独立的 actors 表后,"吴京"只存一条记录,所有电影通过 ID 引用它,改一处全部生效。
这就是关系型数据库的核心思想:通过拆表和关联,消除数据冗余,保证数据一致性。
现在,之前的所有问题都解决了:
| 之前的问题 | 数据库怎么解决 |
|---|---|
| 多标签存不下 | 拆成独立的 tags 表 + 中间表,干净利落 |
| 数据不一致 | 导演是独立的表,引用 ID 而不是手写名字 |
| 关联查询慢 | SQL 的 JOIN 操作,数据库内部优化,毫秒级 |
| 多人协作冲突 | 事务机制 + 行级锁,并发安全 |
| 没有约束 | 主键、外键、NOT NULL、UNIQUE,自动拦截脏数据 |
| 查询效率低 | 索引加速,百万级数据也能秒查 |
小明用一条 SQL 就能查到"吴京演过的所有 8 分以上电影":
sql
SELECT m.title, m.rating
FROM movies m
JOIN movie_actors ma ON m.id = ma.movie_id
JOIN actors a ON ma.actor_id = a.id
WHERE a.name = '吴京' AND m.rating >= 8.0你不需要看懂这条 SQL 的语法——AI 会帮你写。但你需要理解它在做什么:从三张表(电影、中间表、演员)里,通过 ID 关联,找出符合条件的数据。这就是关系型数据库的威力:数据分散存储,查询时动态关联。
这条查询在几百万条数据里也能在毫秒内返回结果——因为数据库会自动使用索引优化查询路径。而同样的操作,用 JSON 文件可能需要几秒甚至几十秒。
演进规律:什么时候该升级?
回顾小明的经历,你会发现一个清晰的规律:不是存储工具不好,而是业务复杂度超过了工具的承载能力。
CSV 在 20 条数据时完美运作,200 条时开始吃力,2000 条时彻底崩溃。JSON 解决了结构灵活性的问题,但在并发和查询效率上又碰了壁。每次升级,都是因为遇到了当前方案解决不了的问题。
不是所有场景都需要数据库。选择存储方式的关键是看你的数据有多复杂:
| 信号 | 说明 | 该用什么 |
|---|---|---|
| 数据不到 100 条,结构简单 | 配置文件、静态清单 | CSV / JSON 文件 |
| 数据之间有关联关系 | 用户→订单→商品 | 关系型数据库 |
| 需要多人同时读写 | Web 应用、API 服务 | 数据库 |
| 需要保证数据正确性 | 金额、库存、用户信息 | 数据库(带约束) |
| 数据量超过几千条 | 查询性能开始下降 | 数据库(带索引) |
| 需要复杂查询 | 统计、排序、分组、关联 | 数据库(SQL) |
一个简单的判断标准:如果你的应用有"用户"的概念,就该用数据库了。 因为有用户就有注册登录,有登录就有会话,有会话就有权限,有权限就有角色……这些关联关系,只有数据库能优雅地处理。
0 / 20 部
CSV 文件
等待中
JSON 文件
等待中
数据库
等待中
反过来说,如果你只是做一个静态网站、一个不需要用户登录的工具页面,或者一个纯前端的计算器,那完全不需要数据库。数据存在浏览器的 localStorage 里,或者写死在代码里就够了。
本节要点
CSV/Excel → JSON → 数据库,不是"越高级越好",而是"业务复杂度决定存储方式"。每种方案都有它的最佳使用场景,关键是认清你的数据有多复杂、有没有关联关系、需不需要多人并发访问。你的 Vibe Coding 项目大概率需要数据库——下一节我们就来学数据库的核心概念。
