---
title: "6.1 数据存储演进"
description: "从 CSV 到 JSON 再到数据库的演进路径"
---

# 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） |

一个简单的判断标准：**如果你的应用有"用户"的概念，就该用数据库了。** 因为有用户就有注册登录，有登录就有会话，有会话就有权限，有权限就有角色……这些关联关系，只有数据库能优雅地处理。

<StorageEvolution />

反过来说，如果你只是做一个静态网站、一个不需要用户登录的工具页面，或者一个纯前端的计算器，那完全不需要数据库。数据存在浏览器的 `localStorage` 里，或者写死在代码里就够了。

---

::: tip 本节要点
CSV/Excel → JSON → 数据库，不是"越高级越好"，而是"业务复杂度决定存储方式"。每种方案都有它的最佳使用场景，关键是认清你的数据有多复杂、有没有关联关系、需不需要多人并发访问。你的 Vibe Coding 项目大概率需要数据库——下一节我们就来学数据库的核心概念。
:::
