反物质维度
69.50M · 2026-03-22
哈喽,掘金的各位全栈练习生们!欢迎来到 AI全栈项目实战的第三天。
在前两天的课程中,也许你已经搞定了前端的页面,或者搭建了简单的 Node.js 服务。但今天,我们要进入一个更加深邃、更加迷人,也绝对是“后端工程师”分水岭的领域 —— 数据库。
如果你觉得数据库只是用来“存个数据”的,那你就太小看它了。在 AI 全栈的架构中,数据库不仅是仓库,它是数据的堡垒,是业务逻辑的最终防线。今天我们的主角,就是被誉为“世界上最先进的开源关系型数据库” —— PostgreSQL(简称 Postgres 或 PG)。
准备好了吗?我们要开始“硬核”但“愉快”的旅程了!
在 NoSQL(如 MongoDB)大行其道的今天,为什么我们依然首选关系型数据库?
想象一下,你正在通过 Excel 管理一个学校。
这些表不是孤立的。学生写文章,文章有评论,评论属于某个学生。这种数据与数据之间存在严密逻辑关联的结构,就是关系型数据库的灵魂。
PostgreSQL 就是这样一个以二维表格(行 Row / 列 Column) 为基础,通过主键 (Primary Key) 和 外键 (Foreign Key) 编织数据网络,并严格遵循 ACID 特性来保证数据“即使天塌下来也不会乱”的神器。
在面试中,ACID 是必考题;在开发中,ACID 是保命符。我们结合一个经典的转账场景(A 转给 B 100元)来彻底搞懂它。
“要么全部成功,要么玉石俱焚。” 事务(Transaction)是数据库操作的最小单位。
“能量守恒定律。” 数据库必须从一个“正确状态”变更为另一个“正确状态”。
“你干你的,我干我的。” 并发执行的事务之间互不干扰。
“落子无悔。” 事务一旦提交(Commit),对数据的改变就是永久的。
Talk is cheap, show me the code. 我们来为博客系统设计两张最核心的表:用户表 (Users) 和 文章表 (Posts)。
我们需要存储用户的 ID、用户名、密码以及注册时间。
看看这段由资深 PG 工程师编写的 SQL:
CREATE TABLE users (
-- 【重点 1】主键与自增
-- id: 用户的唯一标识。
-- BIGINT: 为什么不用 INT?INT 最大约 21 亿。对于大型互联网应用,BIGINT (8字节) 更安全,避免 ID 溢出。
-- GENERATED BY DEFAULT AS IDENTITY: 这是 SQL 标准推荐的自增写法,比旧版的 SERIAL 更规范。
-- PRIMARY KEY: 主键约束,意味着这个字段 1. 唯一 2. 不为空 3. 自带索引。
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
-- 【重点 2】唯一性与非空
-- VARCHAR(255): 变长字符串,节省空间。
-- NOT NULL: 必填项,防止出现“无名氏”。
-- UNIQUE: 唯一约束。数据库会自动为 name 字段建立索引,保证查询速度,同时拒绝重复的用户名注册。
name VARCHAR(255) NOT NULL UNIQUE,
-- 【重点 3】密码安全
-- 永远不要明文存储密码!
-- 预留 255 长度是为了适配 bcrypt 等哈希加密算法生成的长字符串(如 $2b$10$...)。
password VARCHAR(255) NOT NULL,
-- 【重点 4】审计字段(高级工程师的素养)
-- 记录这条数据是什么时候创建的,什么时候最后修改的。
-- TIMESTAMPTZ: 带时区的时间戳 (Timestamp with time zone)。全球化应用必备,它会帮你处理夏令时和跨国时区问题。
-- DEFAULT CURRENT_TIMESTAMP: 如果你不传值,默认填入当前时间。
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
深度解析:
name 加上这个,不仅是为了不重名,更是为了在登录时通过用户名快速查找到用户(数据库不需要全表扫描,而是直接走索引树)。文章属于用户。这里涉及到了一对多关系(一个用户可以写多篇文章)。
CREATE TABLE posts (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
-- 标题不能为空,长度限制适中
title VARCHAR(255) NOT NULL,
-- 内容是长文本,使用 TEXT 类型,没有长度限制,适合存 Markdown 源码
content TEXT,
-- 【重点 5】外键关联
-- 这里存储的是 users 表里的 id。
-- 注意加了双引号 "userId",因为 PG 默认会将未加引号的字段转为小写。如果你想保留驼峰命名,必须加双引号!
"userId" BigInt NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
-- 【重点 6】外键约束 (Foreign Key Constraint)
-- CONSTRAINT fk_posts_user: 给这个约束起个名字,方便报错时定位。
-- FOREIGN KEY ("userId") REFERENCES users(id): 声明 posts 表的 userId 引用自 users 表的 id。
-- ON DELETE CASCADE: 级联删除。这非常关键!
-- 意思是:如果“李白”这个用户被删除了,那么“李白”写的所有文章也会被自动删除。
-- 如果没有这句,删除用户时数据库会报错,提示还有文章依赖于该用户。
CONSTRAINT fk_posts_user FOREIGN KEY ("userId") REFERENCES users(id) ON DELETE CASCADE
);
深度解析:
posts.userId <==> users.id。表建好了,空荡荡的怎么办?我们需要“种子数据”来初始化数据库,方便测试。
注意这里密码已经是加密后的乱码(模拟真实场景)。
INSERT INTO "users" ("id", "name", "password") VALUES
('1', '王皓', '$2b$10$CsO/ykedPpuxqUETBZTYm.F2U4TXDdo01rLmoRPwjKBv3pIL5pnWq'),
('2', '小雪', '$2b$10$CsO/ykedPpuxqUETBZTYm.F2U4TXDdo01rLmoRPwjKBv3pIL5pnWq'),
('3', '李白', '$2b$10$CsO/ykedPpuxqUETBZTYm.F2U4TXDdo01rLmoRPwjKBv3pIL5pnWq'),
-- ... 更多数据
('6', '张三', '$2b$10$CsO/ykedPpuxqUETBZTYm.F2U4TXDdo01rLmoRPwjKBv3pIL5pnWq');
这里我们用诗句来充当内容,充满诗意的数据库!
INSERT INTO "posts" ("title", "content", "userId") VALUES
('黄鹤楼送孟浩然之广陵', '故人西辞黄鹤楼,烟花三月下扬州', 3), -- 李白写的
('春夜喜雨', '好雨知时节,当春乃发生', 4), -- 杜甫写的
('琵琶行', '浔阳江头夜送客,枫叶荻花秋瑟瑟', 5), -- 白居易写的
('静夜思', '床前明月光,疑是地上霜', 3); -- 李白写的
注意:userId 必须是 users 表里已经存在的 id(1-6),否则外键约束会直接报错!这就是 PG 的严谨之处。
数据分表存了,怎么把它们“连”起来看?比如我想看“所有文章以及作者的名字”。这就需要用到 Join。
SELECT posts.title, users.name
FROM posts
LEFT JOIN users ON posts."userId" = users.id;
posts 是左表。显示所有文章。如果某篇文章没有作者(虽然我们的约束不允许,但假设允许),作者栏会显示 NULL。SELECT posts.title, users.name
FROM posts
RIGHT JOIN users ON posts."userId" = users.id;
users 是右表。显示所有用户。如果“张三”没写过文章,他的名字也会出现,但文章标题栏是 NULL。SELECT posts.title, users.name
FROM posts
INNER JOIN users ON posts."userId" = users.id;
作为全栈开发者,不要只依赖图形化工具,命令行才是装 X...哦不,效率的体现!
list 或 l: 列出所有数据库(看一眼你的战利品)。c xuebi: 连接到名为 xuebi 的数据库(进入战场)。dt: 列出当前数据库下的所有表(查看兵力)。d users: 查看 users 表的详细结构(查看兵种属性)。今天我们不仅学习了 SQL 语法,更重要的是理解了数据设计的哲学。
数据库设计没有绝对的“完美”,只有最适合业务的“权衡”。希望通过今天的学习,你在敲下 CREATE TABLE 时,脑海里能浮现出数据在磁盘上井井有条流动的画面。
下节课,我们将拿起 Node.js 这个强大的武器,去连接并操作我们刚刚建立的“数据堡垒”。敬请期待!
如果你喜欢这篇文章,别忘了点赞、收藏、关注三连哦!这对我持续输出高质量技术内容非常重要!