数据库基础概念:理解数据怎么存、怎么关联
完成时间:约 25 分钟。读完后你能看懂 AI 生成的数据模型,判断表结构和关系是否合理。
你不需要会写 SQL,也不需要会建数据库。你需要的是理解数据怎么组织——就像你不需要会搭书架,但要能判断”这本书放在这一层合不合理”。
数据库就是一堆 Excel 表格
Section titled “数据库就是一堆 Excel 表格”还记得 CS 基础篇里说的”仓库和账本”吗?现在我们换一个更贴切的类比,贯穿整篇:
数据库 ≈ 一个 Excel 文件。
| 你熟悉的 Excel | 对应的数据库概念 |
|---|---|
| 整个 Excel 文件 | 数据库 (Database) |
| 一个 Sheet 工作表 | 一张表 (Table) |
| 一行数据 | 一条记录 (Row / Record) |
| 一列 | 一个字段 (Column / Field) |
| 冻结的第一行(表头) | 表结构 / Schema |
但数据库比 Excel 强在三个地方:
- 能存百万行不卡 — Excel 打开 10 万行就开始转圈,数据库处理一百万行眼都不眨
- 多人同时读写 — Excel 一个人打开别人就只能”只读”,数据库允许几百个人同时操作
- 有严格的规则保证数据不出错 — Excel 随便填,数据库可以强制”这个字段必须填”、“这个值不能重复”
核心术语对照表
Section titled “核心术语对照表”先把术语和 Excel 概念对应起来,后面遇到就不慌了:
| 数据库术语 | Excel 类比 | 说明 |
|---|---|---|
| 数据库 (Database) | 整个 Excel 文件 | 一个项目通常一个库 |
| 表 (Table) | 一个 Sheet | users 表、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。这就是两个表之间的”连线”,我们后面会详细讲。
主键 (Primary Key)
Section titled “主键 (Primary Key)”每行数据的”身份证号”,不可重复。
- 就像每个人的身份证号不会跟别人一样,表里每行的主键也不会重复
- 通常是
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"` // ← 显式声明外键关系三种关系类型
Section titled “三种关系类型”表和表之间的关系,只有三种。搞懂了就能看懂任何数据模型。
一对一 (1:1) — 一个对一个
Section titled “一对一 (1:1) — 一个对一个”一个用户有一份个人资料(profile),一份个人资料只属于一个用户。
users 表 profiles 表+----+------+ +----+---------+------+--------+| id | name | | id | user_id | bio | avatar |+----+------+ +----+---------+------+--------+| 1 | 小明 | ←1:1→ | 1 | 1 | ... | ... || 2 | 小红 | ←1:1→ | 2 | 2 | ... | ... |+----+------+ +----+---------+------+--------+什么时候用:当一些信息不常查、或者字段特别多,拆出去让主表保持简洁。
一对多 (1:N) — 最常见
Section titled “一对多 (1:N) — 最常见”一个用户有多条支出记录,但每条支出只属于一个用户。
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)
多对多 (M:N) — 需要”中间表”
Section titled “多对多 (M:N) — 需要”中间表””一个学生选了多门课,一门课也有多个学生。
直接用外键搞不定了(一个字段里不能存多个值),所以需要一个中间表来记录”谁选了哪门课”:
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 只有两个外键,没有自己的业务数据。它的每一行表示”某个学生选了某门课”。
在 Go/GORM 代码里怎么体现
Section titled “在 Go/GORM 代码里怎么体现”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 | 一对多关系的”多”的一方 | ”这个用户下面有多条支出” |
索引 (Index)
Section titled “索引 (Index)”类比:书的目录。
- 没有目录 → 找某个内容要从第 1 页翻到最后一页
- 有了目录 → 直接翻到对应的页码
数据库也一样:
- 没有索引 → 查
email = '[email protected]'要扫描全表每一行 - 有了索引 → 瞬间定位到那一行
在 Go/GORM 里:
Email string `gorm:"index"` // 给 email 建索引Email string `gorm:"uniqueIndex"` // 建索引 + 值不能重复建议:经常用来查询和过滤的字段都应该建索引:
email— 登录时要查user_id— 查某个用户的数据时要用created_at— 按时间排序时要用
代价:索引会占一点存储空间,写入时稍慢。但对于查询的加速效果来说,完全值得。
常见数据类型
Section titled “常见数据类型”AI 生成的 Go 模型里,字段类型和数据库类型的对应关系:
| Go 类型 | 数据库类型 | 用于 | Excel 类比 |
|---|---|---|---|
uint | INTEGER | ID、计数 | 整数格式 |
string | VARCHAR/TEXT | 名字、邮箱、描述 | 文本格式 |
float64 | DECIMAL/FLOAT | 金额 | 数字格式 |
bool | BOOLEAN | 是否激活、是否管理员 | TRUE/FALSE |
time.Time | TIMESTAMP | 创建时间、更新时间 | 日期格式 |
GORM 的自动迁移
Section titled “GORM 的自动迁移”在传统开发中,你需要手写 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 帮你生成了数据模型,用这个清单过一遍:
1. 每个表都有主键 id 吗?
Section titled “1. 每个表都有主键 id 吗?”没有主键的表就像没有行号的 Excel,无法精确定位某一行。
2. 表和表之间的关系对吗?
Section titled “2. 表和表之间的关系对吗?”- 谁有外键?外键放对位置了吗?
- 一对多的关系:外键应该在”多”的那一方(expenses 有 user_id,而不是 users 有 expense_id)
- 多对多的关系:有没有中间表?
3. 关键字段建了索引吗?
Section titled “3. 关键字段建了索引吗?”- email、user_id、created_at 这些经常查的字段要有
gorm:"index"或gorm:"uniqueIndex"
4. 唯一性约束对吗?
Section titled “4. 唯一性约束对吗?”- email 应该不能重复 → 用
uniqueIndex - 用户名要不要唯一?看业务需求
5. 数据类型合理吗?
Section titled “5. 数据类型合理吗?”- 金额用
float64还是decimal?(涉及精度问题,一般项目 float64 够用) - 长文本用
string还是text?(GORM 默认 string 映射 VARCHAR(256),长内容要用gorm:"type:text") - 状态字段是用
string(“active”/“inactive”)还是bool?
拿出一张纸(或打开备忘录),画出一个”记账系统”的数据模型:
- users 表有哪些字段?(id、name、email…还有呢?)
- expenses 表有哪些字段?(id、user_id、amount…还有呢?)
- categories 表要不要单独建一张表?还是在 expenses 里直接用一个 string 字段?
- 它们之间是什么关系?(一对多?多对多?)
画完之后,把你的设计告诉 AI:
我想做一个记账系统,以下是我设计的数据模型:
users 表:...expenses 表:...categories 表:...
请帮我用 Go + GORM 实现这些模型,并解释你做了哪些调整。然后对比你画的和 AI 生成的,看看有哪些差异。差异点就是你的学习收获。
| 概念 | 一句话解释 | Excel 类比 |
|---|---|---|
| 数据库 | 存放所有数据的地方 | 整个 Excel 文件 |
| 表 | 一类数据的集合 | 一个 Sheet |
| 主键 | 每行的唯一标识 | 行号 |
| 外键 | 指向另一张表的”连线” | VLOOKUP 引用 |
| 索引 | 加速查找的”目录” | 排序/筛选 |
| 一对多 | 一个用户有多条记录 | 一个 Sheet 引用另一个 |
| 多对多 | 两边都是”多”,需要中间表 | 三个 Sheet 互相引用 |
| AutoMigrate | 自动根据代码创建表 | 自动生成表头 |
记住:你不需要会建数据库,但要能看懂表结构、判断关系是否合理。用上面的 5 个检查点审查 AI 的输出,就够了。
下一步:回到主线,开始正式课程。