当我们把数据规范化做到极致时,每张表都变得超级精简,里面的信息只遵循一个原则。但实际写查询(比如“哪些学生报名了 SQL 课程?”)时,可能得 JOIN 一堆表。表越多,SQL 越复杂,数据库就得“搬砖”搬得更狠。
你们应该已经在前面的课里见过 JOIN 了。下面是个例子,展示在一个规范化设计的数据库里可能需要的查询:
SELECT students.name, courses.title
FROM students
JOIN enrollments ON students.id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.id
WHERE courses.title = 'SQL';
看起来挺简单,但实际上服务器在背后做了很多事:读每张表、拼数据、过滤……要是表特别大,性能肯定会掉下来。
大战:规范化 vs 速度
幸好(也可能不幸?),现实中的数据库都是妥协的产物。完全规范化能保证数据一致性,但复杂查询会变慢。如果数据库主要用来做分析和报表,有时候反规范化更划算。就像把 10 个小盒子换成一个大箱子:取数据快了,但想再分开就麻烦了。
什么时候可以“松口气”不用太规范?
有些场景下,反规范化更合适:
经常用到的聚合
比如,假设系统每天都要查每门课有多少学生。规范化结构下得一直JOIN 和
COUNT()。不如直接在 "Courses" 表里加个
student_count 字段,每次增删记录时自动更新。
-- 反规范化的字段
UPDATE courses
SET student_count = (
SELECT COUNT(*)
FROM enrollments
WHERE enrollments.course_id = courses.id
);
经常要的报表
如果客户天天要“谁、在哪、啥时候买了啥?”的报表,直接存一张反规范化的表,里面就是“客户名、商品、日期”这种现成的行。表会变大,但查数据飞快。
读多写少的场景
如果数据库主要是读(比如做分析),可以牺牲规范化换速度。
复杂关系下减少 JOIN
如果表之间关系很复杂(嵌套多层),JOIN 写起来很痛苦,可以适当减少规范化层级。
例子:反规范化怎么加速?
假设有个电商的规范化表:
表 products |
表 orders |
表 order_items |
|---|---|---|
| id | id | id |
| name | date | order_id |
| price | customer_id | product_id |
| quantity |
每个订单(orders)包含多条订单项(order_items)。我们来算下商店赚了多少钱:
SELECT SUM(order_items.quantity * products.price) AS total_revenue
FROM order_items
JOIN products ON order_items.product_id = products.id;
在数据量很大的时候,order_items 和 products 的 JOIN 会拖慢查询。
反规范化结构
现在假设 order_items 表里多了个 total_price 字段(反规范化):
表 order_items |
|---|
| id |
| order_id |
| product_id |
| quantity |
| total_price |
现在查询就很简单了:
SELECT SUM(total_price) AS total_revenue
FROM order_items;
这样就不用 JOIN,速度直接起飞。
实战练习:优化“销售”数据库
已知:规范化表
表 products |
表 sales |
|---|---|
| id | id |
| name | product_id |
| price | date |
| quantity |
目标:加速类似“每个产品赚了多少钱?”的高频查询。
第 1 步: 在 sales 表里加个 total_price 字段:
ALTER TABLE sales ADD COLUMN total_price NUMERIC;
第 2 步: 用已有数据填充这个字段:
UPDATE sales
SET total_price = quantity * (
SELECT price
FROM products
WHERE products.id = sales.product_id
);
第 3 步: 查询更快了:
SELECT product_id, SUM(total_price) AS total_revenue
FROM sales
GROUP BY product_id;
但是!反规范化是有代价的
你也知道,“更快”不等于“更好”。反规范化会带来这些问题:
存储冗余
total_price 字段其实是数据的副本,占用更多空间。
更新麻烦
如果 products 表里的价格变了,得手动同步 total_price 字段。不然就会不一致。
插入、更新、删除时的异常
如果忘了同步反规范化的数据,信息很容易“不同步”。比如产品价格变了,但不会自动反映到 total_price。
平衡:怎么找到最佳方案?
先想清楚什么更重要:性能还是结构? 如果数据库主要是读,优先考虑查询效率。
只在关键地方反规范化。 比如只针对核心统计和报表。
自动化反规范化数据的更新。 用触发器或者定时任务,避免数据不一致。
GO TO FULL VERSION