跳转到内容

数据库基础概念:理解数据怎么存、怎么关联

完成时间:约 25 分钟。读完后你能看懂 AI 生成的数据模型,判断表结构和关系是否合理。

你不需要会写 SQL,也不需要会建数据库。你需要的是理解数据怎么组织——就像你不需要会搭书架,但要能判断”这本书放在这一层合不合理”。


还记得 CS 基础篇里说的”仓库和账本”吗?现在我们换一个更贴切的类比,贯穿整篇:

数据库 ≈ 一个 Excel 文件。

你熟悉的 Excel对应的数据库概念
整个 Excel 文件数据库 (Database)
一个 Sheet 工作表一张表 (Table)
一行数据一条记录 (Row / Record)
一列一个字段 (Column / Field)
冻结的第一行(表头)表结构 / Schema

但数据库比 Excel 强在三个地方:

  1. 能存百万行不卡 — Excel 打开 10 万行就开始转圈,数据库处理一百万行眼都不眨
  2. 多人同时读写 — Excel 一个人打开别人就只能”只读”,数据库允许几百个人同时操作
  3. 有严格的规则保证数据不出错 — Excel 随便填,数据库可以强制”这个字段必须填”、“这个值不能重复”

先把术语和 Excel 概念对应起来,后面遇到就不慌了:

数据库术语Excel 类比说明
数据库 (Database)整个 Excel 文件一个项目通常一个库
表 (Table)一个 Sheetusers 表、expenses 表
行 (Row) / 记录 (Record)一行数据一个用户、一条支出
列 (Column) / 字段 (Field)一列name、email、amount
主键 (Primary Key)行号(唯一标识)通常是自增的 id
外键 (Foreign Key)VLOOKUP 的引用列用来关联两个 Sheet
索引 (Index)排序/筛选加速查找的”目录”

想象你打开一个 Excel 文件,里面有两个 Sheet:

Sheet 1:users 表

+----+------+-------------+---------+
| id | name | email | role |
+----+------+-------------+---------+
| 1 | 小明 | [email protected] | admin |
| 2 | 小红 | [email protected] | student |
| 3 | 小刚 | [email protected] | student |
+----+------+-------------+---------+

Sheet 2:expenses 表

+----+---------+--------+----------+------------+
| id | user_id | amount | category | created_at |
+----+---------+--------+----------+------------+
| 1 | 1 | 28.50 | 餐饮 | 2024-01-15 |
| 2 | 1 | 99.00 | 交通 | 2024-01-15 |
| 3 | 2 | 15.00 | 餐饮 | 2024-01-16 |
+----+---------+--------+----------+------------+

注意 expenses 表里的 user_id——它的值(1、1、2)对应 users 表里的 id。这就是两个表之间的”连线”,我们后面会详细讲。


每行数据的”身份证号”,不可重复。

  • 就像每个人的身份证号不会跟别人一样,表里每行的主键也不会重复
  • 通常是 id 字段,自动递增(1, 2, 3…),你不用管它
  • 有了主键,才能精确地说”我要找 id=2 的那个用户”,而不是”我要找第二个用户”(万一顺序变了呢?)

AI 生成的 Go 模型里经常看到这个:

ID uint `gorm:"primaryKey"` // ← 就是在说:这个字段是主键

外键 (Foreign Key):表和表之间的”连线”

Section titled “外键 (Foreign Key):表和表之间的”连线””

回头看 expenses 表里的 user_id

expenses 表第 1 行:user_id = 1 → 指向 users 表里 id=1 的小明
expenses 表第 2 行:user_id = 1 → 也指向小明
expenses 表第 3 行:user_id = 2 → 指向 users 表里 id=2 的小红

含义:这条支出属于哪个用户。

类比:Excel 里你用 VLOOKUP 从另一个 Sheet 拉数据,外键就是数据库版的 VLOOKUP——它说的是”这个值来自另一张表”。

AI 生成的 Go 模型里经常看到这个:

UserID uint `gorm:"index"` // ← 外键,指向 users 表的 id

或者更明确的写法:

UserID uint `gorm:"foreignKey:UserID"` // ← 显式声明外键关系

表和表之间的关系,只有三种。搞懂了就能看懂任何数据模型。

一个用户有一份个人资料(profile),一份个人资料只属于一个用户。

users 表 profiles 表
+----+------+ +----+---------+------+--------+
| id | name | | id | user_id | bio | avatar |
+----+------+ +----+---------+------+--------+
| 1 | 小明 | ←1:1→ | 1 | 1 | ... | ... |
| 2 | 小红 | ←1:1→ | 2 | 2 | ... | ... |
+----+------+ +----+---------+------+--------+

什么时候用:当一些信息不常查、或者字段特别多,拆出去让主表保持简洁。

一个用户有多条支出记录,但每条支出只属于一个用户。

users 表 expenses 表
+----+------+ +----+---------+--------+
| id | name | | id | user_id | amount |
+----+------+ +----+---------+--------+
| 1 | 小明 | ──1:N──→ | 1 | 1 | 28.50 |
| | | | 2 | 1 | 99.00 |
+----+------+ +----+---------+--------+
| 2 | 小红 | ──1:N──→ | 3 | 2 | 15.00 |
+----+------+ +----+---------+--------+

用外键实现:expenses 表里的 user_id 指向 users 表的 id

更多例子:

  • 一个课程有多个课时(lessons.course_id → courses.id)
  • 一个作者有多篇文章(articles.author_id → authors.id)

一个学生选了多门课,一门课也有多个学生。

直接用外键搞不定了(一个字段里不能存多个值),所以需要一个中间表来记录”谁选了哪门课”:

students 表 student_courses 表 courses 表
+----+------+ +------------+-----------+ +----+--------+
| id | name | | student_id | course_id | | id | title |
+----+------+ +------------+-----------+ +----+--------+
| 1 | 小明 | ──→ | 1 | 1 | | 1 | 数学 |
| | | ──→ | 1 | 2 | | 2 | 英语 |
| 2 | 小红 | ──→ | 2 | 1 | | 3 | 物理 |
| | | ──→ | 2 | 3 | +----+--------+
+----+------+ +------------+-----------+

中间表 student_courses 只有两个外键,没有自己的业务数据。它的每一行表示”某个学生选了某门课”。


AI 生成的代码里,数据模型通常长这样。把上面的概念对应起来看:

// users 表 → Go 结构体
type User struct {
ID uint `gorm:"primaryKey"` // 主键:每个用户的唯一标识
Name string // 普通字段:用户名
Email string `gorm:"uniqueIndex"` // 唯一索引:email 不能重复
Role string `gorm:"default:student"` // 默认值:不填就是 student
Expenses []Expense // 一对多:一个用户有多条支出
}
// expenses 表 → Go 结构体
type Expense struct {
ID uint // 主键(GORM 自动识别名为 ID 的字段)
UserID uint `gorm:"index"` // 外键:指向 users.id
Amount float64 // 金额
Category string // 分类
CreatedAt time.Time // 创建时间
}

逐个解释 gorm 标签的含义:

标签含义类比
gorm:"primaryKey"这个字段是主键身份证号
gorm:"uniqueIndex"建索引,且值不能重复Excel 里”不允许重复值”
gorm:"index"建索引(可以重复)给这列加排序,查得更快
gorm:"default:student"不填时的默认值Excel 单元格的默认内容
[]Expense一对多关系的”多”的一方”这个用户下面有多条支出”

类比:书的目录。

  • 没有目录 → 找某个内容要从第 1 页翻到最后一页
  • 有了目录 → 直接翻到对应的页码

数据库也一样:

  • 没有索引 → 查 email = '[email protected]' 要扫描全表每一行
  • 有了索引 → 瞬间定位到那一行

在 Go/GORM 里:

Email string `gorm:"index"` // 给 email 建索引
Email string `gorm:"uniqueIndex"` // 建索引 + 值不能重复

建议:经常用来查询和过滤的字段都应该建索引:

  • email — 登录时要查
  • user_id — 查某个用户的数据时要用
  • created_at — 按时间排序时要用

代价:索引会占一点存储空间,写入时稍慢。但对于查询的加速效果来说,完全值得。


AI 生成的 Go 模型里,字段类型和数据库类型的对应关系:

Go 类型数据库类型用于Excel 类比
uintINTEGERID、计数整数格式
stringVARCHAR/TEXT名字、邮箱、描述文本格式
float64DECIMAL/FLOAT金额数字格式
boolBOOLEAN是否激活、是否管理员TRUE/FALSE
time.TimeTIMESTAMP创建时间、更新时间日期格式

在传统开发中,你需要手写 SQL 来创建表:

CREATE TABLE users (
id INTEGER PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE,
...
);

但 GORM 帮你省了这一步。只需要一行代码:

db.AutoMigrate(&User{}, &Expense{})

GORM 会自动根据 Go 结构体创建或更新数据库表。你改了结构体的字段,重启服务器后 GORM 就会把表结构同步过去。

注意:改了模型后一定要重启服务器,否则数据库不会更新。这是本课程项目的一个常见坑(CLAUDE.md 里的 Gotchas 就提到了这个)。


判断 AI 数据模型对不对的 5 个检查点

Section titled “判断 AI 数据模型对不对的 5 个检查点”

当 AI 帮你生成了数据模型,用这个清单过一遍:

没有主键的表就像没有行号的 Excel,无法精确定位某一行。

  • 谁有外键?外键放对位置了吗?
  • 一对多的关系:外键应该在”多”的那一方(expenses 有 user_id,而不是 users 有 expense_id)
  • 多对多的关系:有没有中间表?
  • email、user_id、created_at 这些经常查的字段要有 gorm:"index"gorm:"uniqueIndex"
  • email 应该不能重复 → 用 uniqueIndex
  • 用户名要不要唯一?看业务需求
  • 金额用 float64 还是 decimal?(涉及精度问题,一般项目 float64 够用)
  • 长文本用 string 还是 text?(GORM 默认 string 映射 VARCHAR(256),长内容要用 gorm:"type:text"
  • 状态字段是用 string(“active”/“inactive”)还是 bool

拿出一张纸(或打开备忘录),画出一个”记账系统”的数据模型:

  1. users 表有哪些字段?(id、name、email…还有呢?)
  2. expenses 表有哪些字段?(id、user_id、amount…还有呢?)
  3. categories 表要不要单独建一张表?还是在 expenses 里直接用一个 string 字段?
  4. 它们之间是什么关系?(一对多?多对多?)

画完之后,把你的设计告诉 AI:

我想做一个记账系统,以下是我设计的数据模型:
users 表:...
expenses 表:...
categories 表:...
请帮我用 Go + GORM 实现这些模型,并解释你做了哪些调整。

然后对比你画的和 AI 生成的,看看有哪些差异。差异点就是你的学习收获。


概念一句话解释Excel 类比
数据库存放所有数据的地方整个 Excel 文件
一类数据的集合一个 Sheet
主键每行的唯一标识行号
外键指向另一张表的”连线”VLOOKUP 引用
索引加速查找的”目录”排序/筛选
一对多一个用户有多条记录一个 Sheet 引用另一个
多对多两边都是”多”,需要中间表三个 Sheet 互相引用
AutoMigrate自动根据代码创建表自动生成表头

记住:你不需要会建数据库,但要能看懂表结构、判断关系是否合理。用上面的 5 个检查点审查 AI 的输出,就够了。


下一步:回到主线,开始正式课程。