跳到主要内容

实战项目:学生成绩管理系统

📖 本节导读

恭喜你一路走到这里!前面我们分章节学习了建库建表、增删改查、函数、聚合分组、多表连接、子查询、约束、索引、事务、视图、用户权限和备份恢复。但知识就像散落的珍珠,只有用一根线把它们串起来,才能变成项链。

这一节,我们就用一个完整的小项目——学生成绩管理系统——把全书知识从头到尾串一遍。你将扮演一名"校园系统开发者",从零开始:

  • 分析需求、设计表结构(这是数据库工作中最值钱的能力);
  • 带着完整约束建库建表;
  • 录入数据;
  • 实现 15 个由易到难的真实业务查询;
  • 创建视图并给任课老师开一个只读账号;
  • 最后用 mysqldump 把整个项目备份。

建议你打开命令行,跟着一步一步敲。做完这个项目,你就可以自信地说:我会用 MySQL 了。


项目目标与需求描述

学校教务处提出了这样的需求:

我们需要一个小系统来管理班级、学生、课程和考试成绩。要能回答这些日常问题:

  1. 某个班有哪些学生?每个班多少人?
  2. 某个学生各科考了多少分?平均分是多少?在全校排第几?
  3. 每门课的平均分、最高分是多少?最高分是谁考的?
  4. 哪些学生不及格?哪些学生没选某门课?
  5. 各班各科的平均分对比如何?
  6. 能不能按分数段统计人数(优秀/良好/中等/及格/不及格)?
  7. 任课老师要能查成绩,但不能改任何数据。
  8. 数据要能定期备份,防止丢失。

需求听起来不少,但别慌——我们一步一步来。


第一步:设计表结构

1. 先找实体(名词)

设计表的第一步是从需求里找名词。名词往往就是"实体",而每个实体通常对应一张表:

  • 班级(class)
  • 学生(student)
  • 课程(course)
  • 成绩(score)

2. 再找关系(实体之间怎么关联)

找到实体后,第二步是想清楚它们之间的关系:

关系类型怎么落到表里
班级 ↔ 学生一对多(一个班有多个学生,一个学生只属于一个班)在"多"的一方(student)加外键 class_id
学生 ↔ 课程多对多(一个学生选多门课,一门课有多个学生)单独建一张中间表,两个外键各指向一方

一对多很好办:谁是"多",就在谁身上加一个外键列,就像每个学生胸前挂一张"我是 X 班的"胸牌。

多对多则必须用中间表。你没办法在 student 表里塞下"他选的所有课",也没办法在 course 表里塞下"选它的所有学生"——那样一个格子里要放多个值,违反了"一个格子只放一个值"的基本原则。

而我们的 score 表恰好就是这张中间表:它一手牵着 student(student_id),一手牵着 course(course_id),顺便还记录了这次关联的"附加信息"——分数。这是多对多中间表非常经典的用法:中间表不仅表达关系,还能携带关系本身的属性。

3. 最终表结构设计

class(班级表)
id 主键
name 班级名,非空、唯一

student(学生表)
id 主键
name 姓名,非空
gender 性别,非空,默认'男'
birthday 出生日期
class_id 外键 → class.id

course(课程表)
id 主键
name 课程名,非空
teacher 任课老师

score(成绩表 = 学生与课程的多对多中间表)
id 主键
student_id 外键 → student.id,非空
course_id 外键 → course.id,非空
score 分数 DECIMAL(5,1),允许 NULL(表示缺考),范围 0~100
联合唯一约束 (student_id, course_id) —— 同一个学生同一门课只能有一条成绩

💡 为什么 score 允许 NULL? 现实中会有"选了课但缺考"的情况。NULL 表示"没有分数",和 0 分(考了但得 0 分)含义完全不同。后面你会看到 NULL 在统计时的特殊表现。


第二步:建库建表(完整 DDL)

前面章节为了聚焦知识点,建表时约束能省则省。这次是"正式项目",我们把学过的约束全部用上:主键、外键、NOT NULL、DEFAULT、CHECK、UNIQUE。

-- 1. 创建数据库(指定 utf8mb4,一劳永逸避免中文乱码)
CREATE DATABASE IF NOT EXISTS school
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_0900_ai_ci;

USE school;

-- 2. 班级表
CREATE TABLE class (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL UNIQUE COMMENT '班级名称'
);

-- 3. 学生表(一对多:多个学生属于一个班级)
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL COMMENT '姓名',
gender CHAR(1) NOT NULL DEFAULT '男' COMMENT '性别',
birthday DATE COMMENT '出生日期',
class_id INT COMMENT '所属班级',
CONSTRAINT fk_student_class
FOREIGN KEY (class_id) REFERENCES class(id)
);

-- 4. 课程表
CREATE TABLE course (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) NOT NULL COMMENT '课程名',
teacher VARCHAR(20) COMMENT '任课老师'
);

-- 5. 成绩表(多对多中间表)
CREATE TABLE score (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL COMMENT '学生id',
course_id INT NOT NULL COMMENT '课程id',
score DECIMAL(5,1) COMMENT '分数,NULL表示缺考',
-- CHECK:分数必须在 0~100 之间(NULL 不受此限制)
CONSTRAINT chk_score CHECK (score BETWEEN 0 AND 100),
-- UNIQUE:同一个学生同一门课只能录一次成绩,防止重复录入
CONSTRAINT uk_student_course UNIQUE (student_id, course_id),
CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id),
CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id)
);

建完后用 SHOW TABLES; 检查一下:

+------------------+
| Tables_in_school |
+------------------+
| class |
| course |
| score |
| student |
+------------------+
4 rows in set

💡 约束就是"防呆装置":CHECK 拦住 120 分这种手滑,UNIQUE 拦住把张三的数学成绩录两遍,外键拦住给不存在的学生录成绩。约束多敲几行字,换来的是数据永远干净。不信你可以故意试试 INSERT INTO score (student_id, course_id, score) VALUES (1, 1, 999);,MySQL 会立刻报错拒绝。


第三步:录入示例数据

在全书统一的 12 条成绩基础上,我们再多加 2 名学生(刘芳、周杰)和 6 条成绩,让后面的统计更有看头:

-- 班级
INSERT INTO class (id, name) VALUES
(1, '一班'),
(2, '二班');

-- 学生(1~5 号是全书出现过的老朋友,6、7 号是新同学)
INSERT INTO student (id, name, gender, birthday, class_id) VALUES
(1, '张三', '男', '2005-03-15', 1),
(2, '李四', '男', '2005-07-01', 1),
(3, '王小红', '女', '2006-01-20', 2),
(4, '赵敏', '女', '2005-11-08', 2),
(5, '陈刚', '男', '2006-05-30', 1),
(6, '刘芳', '女', '2005-09-12', 2),
(7, '周杰', '男', '2006-04-02', 1);

-- 课程
INSERT INTO course (id, name, teacher) VALUES
(1, '数学', '王老师'),
(2, '英语', '李老师'),
(3, '计算机', '张老师');

-- 成绩(前 12 条为全书统一数据,13~18 为新增;注意 12 号陈刚计算机缺考)
INSERT INTO score (id, student_id, course_id, score) VALUES
(1, 1, 1, 90.0),
(2, 1, 2, 85.0),
(3, 1, 3, 88.0),
(4, 2, 1, 76.0),
(5, 2, 2, 92.0),
(6, 3, 1, 95.0),
(7, 3, 2, 60.0),
(8, 3, 3, 70.0),
(9, 4, 1, 58.0),
(10, 4, 3, 81.0),
(11, 5, 2, 73.0),
(12, 5, 3, NULL),
(13, 6, 1, 88.0),
(14, 6, 2, 79.0),
(15, 6, 3, 93.0),
(16, 7, 1, 65.0),
(17, 7, 2, 55.0),
(18, 7, 3, 77.0);

录完检查一下数量:

SELECT
(SELECT COUNT(*) FROM class) AS 班级数,
(SELECT COUNT(*) FROM student) AS 学生数,
(SELECT COUNT(*) FROM course) AS 课程数,
(SELECT COUNT(*) FROM score) AS 成绩条数;
+--------+--------+--------+----------+
| 班级数 | 学生数 | 课程数 | 成绩条数 |
+--------+--------+--------+----------+
| 2 | 7 | 3 | 18 |
+--------+--------+--------+----------+
1 row in set

第四步:15 个由易到难的业务查询

下面是教务处最常问的 15 个问题。每题都按「需求 → SQL → 结果」给出,建议先自己写,再对答案

查询 1:查询一班的全部学生

最基础的条件查询,热热身。

SELECT s.id, s.name, s.gender, s.birthday
FROM student s
JOIN class c ON s.class_id = c.id
WHERE c.name = '一班';
+----+------+--------+------------+
| id | name | gender | birthday |
+----+------+--------+------------+
| 1 | 张三 | 男 | 2005-03-15 |
| 2 | 李四 | 男 | 2005-07-01 |
| 5 | 陈刚 | 男 | 2006-05-30 |
| 7 | 周杰 | 男 | 2006-04-02 |
+----+------+--------+------------+
4 rows in set

查询 2:按年龄从大到小列出所有学生

出生日期越早,年龄越大,所以按 birthday 升序排。

SELECT name, birthday
FROM student
ORDER BY birthday ASC;
+--------+------------+
| name | birthday |
+--------+------------+
| 张三 | 2005-03-15 |
| 李四 | 2005-07-01 |
| 刘芳 | 2005-09-12 |
| 赵敏 | 2005-11-08 |
| 王小红 | 2006-01-20 |
| 周杰 | 2006-04-02 |
| 陈刚 | 2006-05-30 |
+--------+------------+
7 rows in set

查询 3:统计每个班的人数

分组统计的入门题。

SELECT c.name AS 班级, COUNT(s.id) AS 人数
FROM class c
LEFT JOIN student s ON s.class_id = c.id
GROUP BY c.id, c.name;
+------+------+
| 班级 | 人数 |
+------+------+
| 一班 | 4 |
| 二班 | 3 |
+------+------+
2 rows in set

💡 这里用 LEFT JOIN 而不是 JOIN,是为了保证"就算某个班一个学生都没有,也能显示人数 0"。

查询 4:查询张三的各科成绩

三表连接:成绩表左手牵学生、右手牵课程。

SELECT st.name AS 学生, c.name AS 课程, sc.score AS 分数
FROM score sc
JOIN student st ON sc.student_id = st.id
JOIN course c ON sc.course_id = c.id
WHERE st.name = '张三';
+------+--------+------+
| 学生 | 课程 | 分数 |
+------+--------+------+
| 张三 | 数学 | 90.0 |
| 张三 | 英语 | 85.0 |
| 张三 | 计算机 | 88.0 |
+------+--------+------+
3 rows in set

查询 5:查询所有女生的英语成绩

连接 + 多条件过滤。

SELECT st.name AS 学生, sc.score AS 英语成绩
FROM score sc
JOIN student st ON sc.student_id = st.id
JOIN course c ON sc.course_id = c.id
WHERE st.gender = '女' AND c.name = '英语';
+--------+----------+
| 学生 | 英语成绩 |
+--------+----------+
| 王小红 | 60.0 |
| 刘芳 | 79.0 |
+--------+----------+
2 rows in set

(赵敏没有英语成绩记录,所以不会出现——她根本没选英语。)

查询 6:每门课的平均分和实考人数

注意观察 NULL 的表现!

SELECT c.name AS 课程,
ROUND(AVG(sc.score), 1) AS 平均分,
COUNT(sc.score) AS 实考人数,
COUNT(*) AS 选课人数
FROM score sc
JOIN course c ON sc.course_id = c.id
GROUP BY c.id, c.name;
+--------+--------+----------+----------+
| 课程 | 平均分 | 实考人数 | 选课人数 |
+--------+--------+----------+----------+
| 数学 | 78.7 | 6 | 6 |
| 英语 | 74.0 | 6 | 6 |
| 计算机 | 81.8 | 5 | 6 |
+--------+--------+----------+----------+
3 rows in set

💡 计算机课选课 6 人、实考只有 5 人——因为陈刚的成绩是 NULL。COUNT(列名)AVG() 都会自动忽略 NULL,所以计算机平均分是 5 个人的平均(409 ÷ 5 = 81.8),而不是把缺考算 0 分。这正是我们用 NULL 表示缺考的好处。

查询 7:查询不及格名单(分数 < 60)

SELECT st.name AS 学生, c.name AS 课程, sc.score AS 分数
FROM score sc
JOIN student st ON sc.student_id = st.id
JOIN course c ON sc.course_id = c.id
WHERE sc.score < 60
ORDER BY sc.score;
+------+------+------+
| 学生 | 课程 | 分数 |
+------+------+------+
| 周杰 | 英语 | 55.0 |
| 赵敏 | 数学 | 58.0 |
+------+------+------+
2 rows in set

(王小红英语正好 60 分,压线及格,不在名单里。另外 sc.score < 60 对 NULL 的比较结果是"未知",所以缺考的陈刚也不会被误伤。)

查询 8:每门课的最高分是谁考的

难度上一个台阶:需要关联子查询——每一行成绩都去问一句"你是不是你这门课的最高分?"

SELECT c.name AS 课程, st.name AS 学生, sc.score AS 最高分
FROM score sc
JOIN student st ON sc.student_id = st.id
JOIN course c ON sc.course_id = c.id
WHERE sc.score = (
SELECT MAX(score) FROM score WHERE course_id = sc.course_id
);
+--------+--------+--------+
| 课程 | 学生 | 最高分 |
+--------+--------+--------+
| 数学 | 王小红 | 95.0 |
| 英语 | 李四 | 92.0 |
| 计算机 | 刘芳 | 93.0 |
+--------+--------+--------+
3 rows in set

查询 9:查询没选计算机课的学生

"没有"类需求要用 NOT INNOT EXISTS:先找出选了的人,再取反。

SELECT name
FROM student
WHERE id NOT IN (
SELECT student_id FROM score WHERE course_id = 3
);
+------+
| name |
+------+
| 李四 |
+------+
1 row in set

💡 注意区分两种情况:李四在 score 表里根本没有计算机的记录(没选课);而陈刚有记录但分数是 NULL(选了但缺考)。所以陈刚不算"没选"。一行数据存不存在、值是不是 NULL,是两码事。

查询 10:每个学生的平均分并排名

SELECT st.name AS 学生,
ROUND(AVG(sc.score), 1) AS 平均分
FROM student st
JOIN score sc ON sc.student_id = st.id
GROUP BY st.id, st.name
ORDER BY 平均分 DESC;
+--------+--------+
| 学生 | 平均分 |
+--------+--------+
| 张三 | 87.7 |
| 刘芳 | 86.7 |
| 李四 | 84.0 |
| 王小红 | 75.0 |
| 陈刚 | 73.0 |
| 赵敏 | 69.5 |
| 周杰 | 65.7 |
+--------+--------+
7 rows in set

结果自上而下就是名次。如果你用的是 MySQL 8.0+,还可以用窗口函数直接生成名次列:RANK() OVER (ORDER BY AVG(sc.score) DESC) AS 名次

(再次留意 NULL:陈刚只有英语 73 分被计入,缺考的计算机没有拖累他的平均分。)

查询 11:总分前三名

聚合 + 排序 + LIMIT 截取。

SELECT st.name AS 学生, SUM(sc.score) AS 总分
FROM student st
JOIN score sc ON sc.student_id = st.id
GROUP BY st.id, st.name
ORDER BY 总分 DESC
LIMIT 3;
+--------+-------+
| 学生 | 总分 |
+--------+-------+
| 张三 | 263.0 |
| 刘芳 | 260.0 |
| 王小红 | 225.0 |
+--------+-------+
3 rows in set

查询 12:各班各科平均分对比

两个维度(班级 × 课程)同时分组。

SELECT cl.name AS 班级, c.name AS 课程,
ROUND(AVG(sc.score), 1) AS 平均分
FROM score sc
JOIN student st ON sc.student_id = st.id
JOIN class cl ON st.class_id = cl.id
JOIN course c ON sc.course_id = c.id
GROUP BY cl.id, cl.name, c.id, c.name
ORDER BY cl.id, c.id;
+------+--------+--------+
| 班级 | 课程 | 平均分 |
+------+--------+--------+
| 一班 | 数学 | 77.0 |
| 一班 | 英语 | 76.3 |
| 一班 | 计算机 | 82.5 |
| 二班 | 数学 | 80.3 |
| 二班 | 英语 | 69.5 |
| 二班 | 计算机 | 81.3 |
+------+--------+--------+
6 rows in set

可以看出:数学和计算机二班略强或接近,英语一班明显领先。

查询 13:平均分超过全校平均分的学生

子查询做"标尺":先算出全校所有成绩的平均分(1325 ÷ 17 ≈ 77.9),再拿每个人的平均分去比。

SELECT st.name AS 学生,
ROUND(AVG(sc.score), 1) AS 平均分
FROM student st
JOIN score sc ON sc.student_id = st.id
GROUP BY st.id, st.name
HAVING AVG(sc.score) > (SELECT AVG(score) FROM score)
ORDER BY 平均分 DESC;
+------+--------+
| 学生 | 平均分 |
+------+--------+
| 张三 | 87.7 |
| 刘芳 | 86.7 |
| 李四 | 84.0 |
+------+--------+
3 rows in set

💡 对聚合结果做过滤要用 HAVING 而不是 WHERE——WHERE 在分组前筛"行",HAVING 在分组后筛"组"。

查询 14:至少有两门课在 80 分以上的学生

先过滤(WHERE),再分组计数(GROUP BY + COUNT),再筛组(HAVING)——三板斧连招。

SELECT st.name AS 学生, COUNT(*) AS `80分以上课程数`
FROM student st
JOIN score sc ON sc.student_id = st.id
WHERE sc.score >= 80
GROUP BY st.id, st.name
HAVING COUNT(*) >= 2;
+------+----------------+
| 学生 | 80分以上课程数 |
+------+----------------+
| 张三 | 3 |
| 刘芳 | 2 |
+------+----------------+
2 rows in set

查询 15:用 CASE WHEN 统计成绩等级分布

把连续的分数"装箱"成等级,是报表里最常见的写法。

SELECT
CASE
WHEN score >= 90 THEN '优秀(90-100)'
WHEN score >= 80 THEN '良好(80-89)'
WHEN score >= 70 THEN '中等(70-79)'
WHEN score >= 60 THEN '及格(60-69)'
ELSE '不及格(<60)'
END AS 等级,
COUNT(*) AS 人次
FROM score
WHERE score IS NOT NULL
GROUP BY 等级
ORDER BY MIN(score) DESC;
+--------------+------+
| 等级 | 人次 |
+--------------+------+
| 优秀(90-100) | 4 |
| 良好(80-89) | 4 |
| 中等(70-79) | 5 |
| 及格(60-69) | 2 |
| 不及格(<60) | 2 |
+--------------+------+
5 rows in set

合计 17 人次,正好等于 18 条成绩去掉 1 条缺考(NULL)。注意 WHERE score IS NOT NULL 很关键——不加的话,缺考的 NULL 会掉进 ELSE 分支被错算成"不及格"。

15 个查询全部完成!回头看看:单表查询 → 排序 → 分组 → 多表连接 → 子查询 → HAVING → CASE WHEN,你已经把查询篇的武器库完整地用了一遍。


第五步:视图 + 只读账号,交付给任课老师

1. 创建成绩单视图

老师们不关心 id 和外键,只想看"人话版"成绩单。我们把三表连接的复杂查询封装成一个视图,以后查它就像查一张普通表:

CREATE VIEW v_score_report AS
SELECT cl.name AS 班级,
st.name AS 学生,
c.name AS 课程,
c.teacher AS 任课老师,
sc.score AS 分数
FROM score sc
JOIN student st ON sc.student_id = st.id
JOIN class cl ON st.class_id = cl.id
JOIN course c ON sc.course_id = c.id;

用起来非常清爽:

SELECT * FROM v_score_report WHERE 课程 = '数学' ORDER BY 分数 DESC;
+------+--------+------+----------+------+
| 班级 | 学生 | 课程 | 任课老师 | 分数 |
+------+--------+------+----------+------+
| 二班 | 王小红 | 数学 | 王老师 | 95.0 |
| 一班 | 张三 | 数学 | 王老师 | 90.0 |
| 二班 | 刘芳 | 数学 | 王老师 | 88.0 |
| 一班 | 李四 | 数学 | 王老师 | 76.0 |
| 一班 | 周杰 | 数学 | 王老师 | 65.0 |
| 二班 | 赵敏 | 数学 | 王老师 | 58.0 |
+------+--------+------+----------+------+
6 rows in set

2. 创建只读用户并授权

需求里说:老师能查、不能改。这就是权限管理的用武之地——建一个账号,只给它这个视图的 SELECT 权限:

-- 创建用户
CREATE USER 'teacher'@'localhost' IDENTIFIED BY 'Teach@2026';

-- 只授予对成绩单视图的查询权限
GRANT SELECT ON school.v_score_report TO 'teacher'@'localhost';

-- 刷新权限并确认
FLUSH PRIVILEGES;
SHOW GRANTS FOR 'teacher'@'localhost';
+------------------------------------------------------------------------+
| Grants for teacher@localhost |
+------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `teacher`@`localhost` |
| GRANT SELECT ON `school`.`v_score_report` TO `teacher`@`localhost` |
+------------------------------------------------------------------------+
2 rows in set

现在用 mysql -u teacher -p 登录测试:查视图一切正常;但只要尝试 UPDATE 成绩、甚至 SELECT * FROM score(直接查底层表),都会收到 ERROR 1142 ... command denied 的拒绝。视图 + 最小权限,既方便了老师,又保护了数据——这是生产环境里非常标准的做法。


第六步:用 mysqldump 备份整个项目

辛苦搭建的系统,最后一定要养成备份的习惯。退出 mysql 客户端,在系统命令行执行:

mysqldump -u root -p --databases school > school_backup_20260726.sql

输入密码后,当前目录会生成一个 SQL 文件,里面是重建整个库所需的全部 CREATEINSERT 语句。验证一下它能不能救命——恢复演练:

mysql -u root -p < school_backup_20260726.sql

💡 备份的三个好习惯:① 文件名带日期,别互相覆盖;② 定期做,别只做一次;③ 至少演练过一次恢复——没验证过的备份等于没有备份。

至此,学生成绩管理系统从设计、建表、录数据、查询、交付到备份,完整走通了一遍。🎉


📝 小结

  • 表设计口诀:先找实体(名词)→ 再找关系;一对多在"多"方加外键,多对多必须用中间表,score 就是携带属性(分数)的中间表。
  • 正式项目建表要带全约束:主键、外键、NOT NULL、DEFAULT、CHECK (score BETWEEN 0 AND 100)UNIQUE (student_id, course_id),让脏数据根本进不来。
  • NULL 表示"没有"而不是 0:AVG/COUNT(列) 自动忽略它;判断要用 IS NULL;"没选课"(无记录)和"缺考"(有记录值为 NULL)是两回事。
  • 复杂查询的套路是组合拳:JOIN 连表 → WHERE 筛行 → GROUP BY 分组 → HAVING 筛组 → ORDER BY 排序 → LIMIT 截取,再加子查询和 CASE WHEN 应对"标尺比较"和"分段统计"。
  • 交付系统的标准姿势:视图封装复杂查询 + 只读账号最小授权 + mysqldump 定期备份。

🎓 结课寄语

看到这里,请先停下来,为自己鼓个掌。👏

从第一次敲下 mysql -u root -p,到今天独立完成一个带完整约束、15 个业务查询、视图、权限和备份的项目——你已经具备了绝大多数开发岗位要求的 MySQL 基本功。零基础到这里,靠的不是天赋,是你一节一节跟着敲下来的坚持。

数据库的世界还很大,给你几条后续学习路线参考:

  1. 存储过程与触发器:把一串 SQL 封装成"函数"存在数据库里,让数据库自己响应事件。
  2. 性能优化与 EXPLAIN 深入:数据量到百万级后,学会看执行计划、设计索引、定位慢查询,是初级与高级的分水岭。
  3. 在编程语言中操作 MySQL:用 Python(pymysql)、Java(JDBC/MyBatis)或 Node.js 连接数据库,SQL 才真正融入应用开发。
  4. 认识更多存储伙伴:缓存数据库 Redis、文档数据库 MongoDB……理解"什么场景选什么存储"。
  5. 读一读官方文档:dev.mysql.com/doc 永远是最权威的答案来源。

数据是这个时代的石油,而你已经拿到了开采它的第一把钻头。祝你在数据的世界里越走越远。江湖再见!


⬅ 上一节 | 🏠 返回目录 | 下一节 ➡