Skip to content

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.jsontsconfig.json 都是它。

JSON 比 CSV 灵活得多,最大的优势是支持嵌套结构数组。小明之前在 CSV 里搞不定的"一部电影多个标签"问题,在 JSON 里轻松解决:

json
{
  "movies": [
    {
      "id": 1,
      "title": "流浪地球",
      "director": "郭帆",
      "rating": 7.9,
      "tags": ["科幻", "灾难", "国产"],
      "review": "特效炸裂",
      "actors": ["吴京", "屈楚萧", "李光洁"]
    }
  ]
}

标签和演员可以用数组了,嵌套结构也能表达了。小明很开心,觉得终于找到了完美方案。

但当他把应用做成 Web 版、开始有用户访问时,新的问题又来了:

问题一:查询效率低

想查"所有评分 8 分以上的科幻片",程序需要把整个 JSON 文件读进内存,然后一条一条遍历,检查每条记录的 ratingtags。数据量小的时候感觉不到,但当小明的电影库涨到几万条时,每次查询都要遍历整个文件,页面加载明显变慢了。

数据库不会有这个问题——它有索引机制,可以直接跳到符合条件的数据,不需要逐条扫描。这就像在一本书里找某个关键词:没有目录就只能从头翻到尾,有了目录就能直接翻到对应页码。

问题二:没有数据约束

JSON 文件不会阻止你写入错误数据。评分写成 "很高" 而不是数字?演员 ID 指向一个不存在的人?文件不会报错,数据就这么悄悄脏了。你可能几个月后才发现某些数据是错的,但那时候已经不知道是什么时候、被谁写错的了。

数据库有约束机制——你可以定义"评分必须是 0-10 之间的数字"、"演员 ID 必须指向一个真实存在的演员",违反规则的数据根本写不进去。

问题三:并发写入会丢数据

两个请求同时读取 JSON 文件、各自修改、各自写回——后写的那个会覆盖先写的修改。这在 Web 应用里是致命的。想象两个用户同时给不同的电影打分,结果只有一个人的打分被保存了,另一个人的修改凭空消失。

这个问题叫竞态条件(Race Condition)——你在电商大促时抢过最后一件库存吗?两个人同时点"立即购买",谁能下单成功取决于谁的请求先到服务器,结果不可预测。这是并发编程中最经典的 bug 之一。数据库通过事务和锁机制从根本上解决了这个问题——事务保证"一组操作要么全成功要么全撤销",锁保证"同一时刻只有一个人能改这条数据"。

问题四:关联查询很痛苦

想查"吴京演过的所有电影的平均分",需要遍历所有电影、检查演员数组里有没有"吴京"、收集评分、算平均值。代码写起来又长又慢。如果数据分散在多个 JSON 文件里(电影一个文件、演员一个文件),关联起来更麻烦——你得自己写代码把两个文件的数据"拼"在一起。

数据库有专门的 JOIN 操作来做这件事,一条语句就能把多张表的数据关联起来,而且数据库内部会自动优化查询路径,比你手写的遍历代码快几个数量级。

JSON 适合什么?应用的配置文件(package.jsontsconfig.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 项目大概率需要数据库——下一节我们就来学数据库的核心概念。