后端开发绕不开数据库,而 MySQL 是绝大多数人接触的第一款关系型数据库。从一个小型博客到千万用户的电商平台,MySQL 都在背后默默存着每一行数据。本文带你从"数据库是什么"开始,到亲手建库建表、插入查询数据,搭建一个完整的"学生选课系统"数据模型。不需要任何前置知识。
为什么需要数据库
写一个简单的脚本时,把数据存到文本文件或 Excel 里确实够用。来对比一下这两种方式的差异:
| 场景 | 纯文件/Excel | 数据库 |
|---|---|---|
| 查询"所有及格的学生的姓名" | 写代码遍历整个文件 | 一条 SELECT 语句 |
| 多个用户同时写入 | 文件锁、数据覆盖、竞态条件 | 数据库自动处理并发 |
| 防止存入不合法的数据 | 全靠代码校验,换个入口就绕过 | 数据库约束兜底 |
| 数据量到百万级 | 一个 Excel 文件打不开 | 照常运行 |
| 误删后找回 | 取决于有没有备份文件 | 事务回滚、日志恢复 |
数据库本质上是一个专门管理数据的独立程序。你把数据交给它,它帮你安全地存、高效地查、并发地写,同时保证数据不会乱。
关系型数据库与 MySQL
什么是关系型数据库
"关系型"这个词听起来抽象,其实就对应一个你非常熟悉的东西------表格。
┌──────────────────────────────────────────────────────┐
│ 学生表 (students) │
├────┬──────────┬─────┬──────────────────┬──────────────┤
│ id │ name │ age │ email │ enrolled_at │
├────┼──────────┼─────┼──────────────────┼──────────────┤
│ 1 │ 张三 │ 20 │ zhangsan@qq.com │ 2024-09-01 │
│ 2 │ 李四 │ 21 │ lisi@qq.com │ 2024-09-01 │
│ 3 │ 王五 │ 19 │ wangwu@qq.com │ 2024-09-01 │
└────┴──────────┴─────┴──────────────────┴──────────────┘
每一行是一个学生,每一列是学生的某个属性。关系型数据库就是由这样一张张表格组成的系统,表与表之间通过"关系"连接------比如一张"选课表"同时关联学生和课程。
这引出了一个核心概念------SQL (Structured Query Language):一种专门用来和关系型数据库对话的语言。你用 SQL 告诉数据库"帮我查一下张三选了哪些课",它返回结果。所有主流关系型数据库(MySQL、PostgreSQL、Oracle、SQL Server)都使用 SQL,学会 SQL 就意味着你能操作其中任何一种。
MySQL 的位置
MySQL 是关系型数据库中的"瑞士军刀"------轻量、免费、文档多、社区大。Facebook 早期用它,国内互联网公司的标配之一。现在由 Oracle 维护,同时有开源的社区版和完全兼容的替代品 MariaDB。本文以 MySQL 8.0 社区版为准。
关系型数据库还有很多选择:PostgreSQL 在功能完整性和标准兼容性上更强,SQLite 是一个零配置的文件数据库(Django 默认用它做开发)。MySQL 的优势在于部署简单、读性能好、运维工具成熟,作为入门选择非常合适。
环境准备:安装 MySQL
用 Docker 是最简单的方式------一条命令就能跑起来,不会弄乱系统环境。
bash
docker run -d \
--name mysql-tutorial \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_DATABASE=school \
mysql:8.0
参数说明:---name 给容器命名,-p 3306:3306 把容器的 MySQL 端口映射到本机,-e MYSQL_ROOT_PASSWORD 设置 root 密码,-e MYSQL_DATABASE 启动时自动创建一个叫 school 的数据库。执行完等几秒,MySQL 就准备好了。
连进去看看:
bash
docker exec -it mysql-tutorial mysql -uroot -p123456
出现 mysql> 提示符就说明连接成功。后续所有 SQL 语句都在这个交互式终端里执行。
如果不想用 Docker,也可以去 dev.mysql.com/downloads 下载安装包。安装过程中会要求设置 root 密码,记住它,后面登录用的就是 mysql -uroot -p。
核心概念拆解
在写 SQL 之前,先理清几个基本概念。它们之间的关系可以概括为:
数据库 (database)
└── 表 (table)
├── 列 (column) / 字段 (field) ------ 定义数据的结构
└── 行 (row) / 记录 (record) ------ 实际存的数据
└── 主键 (primary key) ------ 每行的唯一标识
└── 外键 (foreign key) ------ 指向另一张表的数据行
数据库 (Database)
数据库是表的容器,一个数据库里可以有多张表。 你可以把数据库理解为一个项目的命名空间------"学生管理系统"用一个库,"电商系统"用另一个库,互不干扰。
sql
-- 查看有哪些数据库
SHOW DATABASES;
你会看到几个系统自带的库(mysql、information_schema 等),以及我们创建容器时指定的 school。
表 (Table)
表是数据存储的实际载体。在建表之前,你得先决定有哪些列、每列是什么类型。这一步叫定义表结构(schema),是整个数据库设计的起点。
主键 (Primary Key)
主键是表中每行数据的唯一身份证号。 它有两个铁律:
- 不能为空:每行必须有一个主键值
- 不能重复:两张不同的行不能有相同的主键
最常见的做法是设一个自增整数 id 作为主键,每次插入新行时自动 +1。
外键 (Foreign Key)
外键是表与表之间建立关系的机制。 比如"选课记录表"里有一个 student_id 列,它的值必须来自"学生表"的某个主键 id------这个 student_id 就是外键。
外键的好处是数据库会帮你把关:比如你不能插入一条选课记录,引用一个不存在的学生 ID。这种"引用完整性"是关系型数据库的核心价值之一。
认识数据类型
建表之前必须知道有哪些数据类型可用。MySQL 的数据类型可以分成三大类:
数值类型
| 类型 | 范围 | 典型用途 |
|---|---|---|
TINYINT |
-128 ~ 127 | 年龄、状态码 |
INT |
-21亿 ~ 21亿 | 数量、ID |
BIGINT |
极大整数 | 大表主键、时间戳 |
DECIMAL(M, D) |
精确小数 | 金额(关键:金额永远不用 FLOAT) |
FLOAT / DOUBLE |
近似浮点 | 科学计算、评分 |
金额为什么不能用 FLOAT?因为浮点数存储的是近似值,
0.1 + 0.2在浮点世界里不等于0.3。金额计算差一分钱都是事故,所以用精确的DECIMAL。
字符串类型
| 类型 | 特点 | 典型用途 |
|---|---|---|
CHAR(N) |
定长,最多 255 字符 | 性别、状态码等固定长度的字段 |
VARCHAR(N) |
变长,最多 65535 字符 | 姓名、邮箱、标题等变长文本 |
TEXT |
长文本 | 文章正文、简介 |
VARCHAR 是最常用的字符串类型 。VARCHAR(50) 表示最多存 50 个字符,但实际只占用你存进去的长度 + 1 字节,不会浪费空间。
日期时间类型
| 类型 | 格式 | 说明 |
|---|---|---|
DATE |
YYYY-MM-DD |
只存日期 |
TIME |
HH:MM:SS |
只存时间 |
DATETIME |
YYYY-MM-DD HH:MM:SS |
日期+时间,范围 1000~9999 年 |
TIMESTAMP |
同上 | 日期+时间,范围 1970~2038 年,自动跟随时区 |
日常开发里 DATETIME 用得最多------范围大、行为直观。TIMESTAMP 在需要"记录何时创建、何时更新"且希望数据库自动维护的场景下更合适。
第一条 SQL:建库建表
创建并使用数据库
sql
-- 创建一个数据库
CREATE DATABASE school;
-- 切换到该数据库(后续操作都针对它)
USE school;
因为我们用 Docker 启动时指定了 MYSQL_DATABASE=school,库已经存在,直接用 USE school; 即可。
创建学生表
sql
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age TINYINT,
email VARCHAR(100) UNIQUE,
enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
逐行解读:
id INT AUTO_INCREMENT PRIMARY KEY:整数主键,每次插入时自动 +1name VARCHAR(50) NOT NULL:名字最多 50 个字符,不能为空age TINYINT:年龄,没有NOT NULL,允许不填email VARCHAR(100) UNIQUE:邮箱,不能重复enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP:入学时间,不指定时自动填当前时间
创建课程表
sql
CREATE TABLE courses (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
credit TINYINT NOT NULL DEFAULT 1,
teacher VARCHAR(50) NOT NULL
);
每门课有名称、学分和授课老师。DEFAULT 1 表示不填学分时默认为 1 分。
创建选课表
这是"学生"和"课程"之间的桥梁------一个学生可以选多门课,一门课可以有多个学生,这是典型的多对多关系。
sql
CREATE TABLE enrollments (
id INT AUTO_INCREMENT PRIMARY KEY,
student_id INT NOT NULL,
course_id INT NOT NULL,
score DECIMAL(4, 1),
enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
关键点:
score DECIMAL(4, 1):分数,总共 4 位数字,其中 1 位是小数(如85.5、100.0)FOREIGN KEY (student_id) REFERENCES students(id):student_id必须是students表中存在的id;如果试图插入一个不存在的学生 ID,数据库会直接拒绝FOREIGN KEY (course_id) REFERENCES courses(id):同理,课程 ID 必须存在
执行 SHOW TABLES; 确认三张表都已创建:
sql
SHOW TABLES;
-- +------------------+
-- | Tables_in_school |
-- +------------------+
-- | courses |
-- | enrollments |
-- | students |
-- +------------------+
再用 DESCRIBE 或 SHOW COLUMNS FROM 可以查看任意表的结构:
sql
DESCRIBE students;
数据操作:增删改查
现在三张空表就绪,正式开始往里面填数据。
INSERT:插入数据
sql
-- 插入学生
INSERT INTO students (name, age, email) VALUES
('张三', 20, 'zhangsan@example.com'),
('李四', 21, 'lisi@example.com'),
('王五', 19, 'wangwu@example.com');
-- 插入课程
INSERT INTO courses (name, credit, teacher) VALUES
('高等数学', 4, '陈教授'),
('大学英语', 3, '刘老师'),
('数据库原理', 3, '张教授'),
('数据结构', 4, '王教授');
注意 INSERT 的语法:INSERT INTO 表名 (列1, 列2, ...) VALUES (值1, 值2, ...)。id 字段是自增的,不用手动指定;enrolled_at 有默认值,也不用写。
再插入选课记录,建立学生和课程之间的关联:
sql
-- 张三选了高等数学和数据库原理
INSERT INTO enrollments (student_id, course_id, score) VALUES
(1, 1, 92.5), -- 张三 → 高等数学
(1, 3, 88.0); -- 张三 → 数据库原理
-- 李四选了三门课
INSERT INTO enrollments (student_id, course_id, score) VALUES
(2, 1, 76.0), -- 李四 → 高等数学
(2, 2, 85.5), -- 李四 → 大学英语
(2, 4, NULL); -- 李四 → 数据结构(还没出成绩)
-- 王五选了两门
INSERT INTO enrollments (student_id, course_id, score) VALUES
(3, 3, 91.0), -- 王五 → 数据库原理
(3, 4, 79.0); -- 王五 → 数据结构
当你要插入很多行时,一条
INSERT塞多个VALUES比分开写多条INSERT高效得多。数据库事务的开销集中在语句边界上,批量插入能显著减少这个开销。
SELECT:查询数据
SELECT 是 SQL 里最复杂也最重要的语句。这里先讲基础用法,复杂查询留到下一篇文章展开。
查全表:
sql
SELECT * FROM students;
* 表示所有列。输出:
+----+--------+------+------------------------+---------------------+
| id | name | age | email | enrolled_at |
+----+--------+------+------------------------+---------------------+
| 1 | 张三 | 20 | zhangsan@example.com | 2026-07-09 18:00:00 |
| 2 | 李四 | 21 | lisi@example.com | 2026-07-09 18:00:00 |
| 3 | 王五 | 19 | wangwu@example.com | 2026-07-09 18:00:00 |
+----+--------+------+------------------------+---------------------+
只查特定列:
sql
SELECT name, email FROM students;
加条件筛选(WHERE):
sql
-- 查年龄大于等于 20 的学生
SELECT name, age FROM students WHERE age >= 20;
-- 查选了课程 1(高等数学)的选课记录
SELECT * FROM enrollments WHERE course_id = 1;
排序(ORDER BY):
sql
-- 按年龄降序排列
SELECT name, age FROM students ORDER BY age DESC;
-- 按入学时间升序(默认就是 ASC,可以不写)
SELECT name, enrolled_at FROM students ORDER BY enrolled_at ASC;
限制返回行数(LIMIT):
sql
-- 只查年龄最大的两个学生
SELECT name, age FROM students ORDER BY age DESC LIMIT 2;
UPDATE:修改数据
sql
-- 王五过生日,年龄 +1
UPDATE students SET age = 20 WHERE name = '王五';
WHERE 子句是 UPDATE 的保险丝。 如果你写了 UPDATE students SET age = 20; 却忘了 WHERE,数据库会忠实地把所有人的年龄都改成 20。
当你执行 UPDATE 或 DELETE 时,先写
WHERE条件再补前面的部分,或者先在SELECT里跑一遍相同的条件确认命中范围------这是很多 DBA 用血泪换来的肌肉记忆。
DELETE:删除数据
sql
-- 退选:删除某条选课记录
DELETE FROM enrollments WHERE student_id = 3 AND course_id = 4;
同理,DELETE 必须带 WHERE 。DELETE FROM students; 会清空整张表。万一真的误删了,还有最后一根救命稻草:
sql
-- 开启事务后再操作,确认无误再提交
START TRANSACTION;
DELETE FROM enrollments WHERE student_id = 1;
-- 检查一下删对了没
SELECT * FROM enrollments WHERE student_id = 1;
-- 确认无误再提交,搞错了就 ROLLBACK
COMMIT;
事务的概念在第 5 篇会详细展开,现在只需要记住这个保命用法。
实战:学生选课系统
把前面学到的所有操作串起来,完整地走一遍"建库 → 建表 → 插数据 → 查询"的流程。
第一轮:基础查询
问题 1:查所有课程及学分
sql
SELECT name, credit FROM courses;
问题 2:查选了高等数学(course_id=1)的学生的成绩,按分数降序
sql
SELECT student_id, score
FROM enrollments
WHERE course_id = 1
ORDER BY score DESC;
问题 3:找出还没有成绩的选课记录
sql
SELECT * FROM enrollments WHERE score IS NULL;
注意:判断 NULL 不能用 = 或 !=,必须用 IS NULL 或 IS NOT NULL。NULL 在 SQL 里的含义是"未知",任何值与 NULL 比较的结果也是 NULL(不是 true 也不是 false),所以 score = NULL 永远不成立。
第二轮:稍微进阶一点
问题 4:查每门课的平均分
sql
SELECT course_id, AVG(score) AS avg_score
FROM enrollments
WHERE score IS NOT NULL
GROUP BY course_id;
AVG() 是聚合函数,AS avg_score 给计算结果起个别名,GROUP BY course_id 表示按课程分组计算。
问题 5:查平均分大于 80 的课程
sql
SELECT course_id, AVG(score) AS avg_score
FROM enrollments
WHERE score IS NOT NULL
GROUP BY course_id
HAVING avg_score > 80;
WHERE 和 HAVING 的区别:WHERE 在分组之前 过滤行,HAVING 在分组之后 过滤组。这里用 HAVING 是因为我们要对 AVG(score) 的结果做筛选,而 AVG 的结果在分组之后才有。
问题 6:查张三选了哪些课(课程名、成绩)
sql
SELECT c.name, e.score
FROM enrollments e
JOIN courses c ON e.course_id = c.id
WHERE e.student_id = 1;
这是你写下的第一条 JOIN 语句。它的逻辑是:把 enrollments 表和 courses 表按 course_id = id 的条件拼在一起,这样选课记录里就能直接带上课程名称。
这些 SQL 之间的关系
#mermaid-svg-TXDDqgMM1AGgXIMq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TXDDqgMM1AGgXIMq .error-icon{fill:#552222;}#mermaid-svg-TXDDqgMM1AGgXIMq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TXDDqgMM1AGgXIMq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TXDDqgMM1AGgXIMq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TXDDqgMM1AGgXIMq .marker.cross{stroke:#333333;}#mermaid-svg-TXDDqgMM1AGgXIMq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TXDDqgMM1AGgXIMq p{margin:0;}#mermaid-svg-TXDDqgMM1AGgXIMq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TXDDqgMM1AGgXIMq .cluster-label text{fill:#333;}#mermaid-svg-TXDDqgMM1AGgXIMq .cluster-label span{color:#333;}#mermaid-svg-TXDDqgMM1AGgXIMq .cluster-label span p{background-color:transparent;}#mermaid-svg-TXDDqgMM1AGgXIMq .label text,#mermaid-svg-TXDDqgMM1AGgXIMq span{fill:#333;color:#333;}#mermaid-svg-TXDDqgMM1AGgXIMq .node rect,#mermaid-svg-TXDDqgMM1AGgXIMq .node circle,#mermaid-svg-TXDDqgMM1AGgXIMq .node ellipse,#mermaid-svg-TXDDqgMM1AGgXIMq .node polygon,#mermaid-svg-TXDDqgMM1AGgXIMq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TXDDqgMM1AGgXIMq .rough-node .label text,#mermaid-svg-TXDDqgMM1AGgXIMq .node .label text,#mermaid-svg-TXDDqgMM1AGgXIMq .image-shape .label,#mermaid-svg-TXDDqgMM1AGgXIMq .icon-shape .label{text-anchor:middle;}#mermaid-svg-TXDDqgMM1AGgXIMq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TXDDqgMM1AGgXIMq .rough-node .label,#mermaid-svg-TXDDqgMM1AGgXIMq .node .label,#mermaid-svg-TXDDqgMM1AGgXIMq .image-shape .label,#mermaid-svg-TXDDqgMM1AGgXIMq .icon-shape .label{text-align:center;}#mermaid-svg-TXDDqgMM1AGgXIMq .node.clickable{cursor:pointer;}#mermaid-svg-TXDDqgMM1AGgXIMq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TXDDqgMM1AGgXIMq .arrowheadPath{fill:#333333;}#mermaid-svg-TXDDqgMM1AGgXIMq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TXDDqgMM1AGgXIMq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TXDDqgMM1AGgXIMq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TXDDqgMM1AGgXIMq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TXDDqgMM1AGgXIMq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TXDDqgMM1AGgXIMq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TXDDqgMM1AGgXIMq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TXDDqgMM1AGgXIMq .cluster text{fill:#333;}#mermaid-svg-TXDDqgMM1AGgXIMq .cluster span{color:#333;}#mermaid-svg-TXDDqgMM1AGgXIMq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TXDDqgMM1AGgXIMq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TXDDqgMM1AGgXIMq rect.text{fill:none;stroke-width:0;}#mermaid-svg-TXDDqgMM1AGgXIMq .icon-shape,#mermaid-svg-TXDDqgMM1AGgXIMq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TXDDqgMM1AGgXIMq .icon-shape p,#mermaid-svg-TXDDqgMM1AGgXIMq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TXDDqgMM1AGgXIMq .icon-shape .label rect,#mermaid-svg-TXDDqgMM1AGgXIMq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TXDDqgMM1AGgXIMq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TXDDqgMM1AGgXIMq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TXDDqgMM1AGgXIMq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CREATE DATABASE school
CREATE TABLE students / courses / enrollments
INSERT INTO 填充数据
SELECT 基础查询
SELECT + WHERE 条件过滤
SELECT + GROUP BY 聚合统计
JOIN 联表查询
UPDATE / DELETE 修改与删除
从建库到 JOIN,SQL 的学习路径就是一条逐步"组合"的路:先会操作单表,再学会过滤和排序,最后把多张表拼起来。
常见错误速查
| 错误信息 | 原因 | 解决 |
|---|---|---|
Table 'xxx' already exists |
重复建表 | 先 DROP TABLE IF EXISTS xxx; 再 CREATE |
Unknown column 'xxx' in 'field list' |
列名拼写错误 | 用 DESCRIBE 表名; 核对列名 |
Cannot add or update a child row: a foreign key constraint fails |
外键引用了不存在的 ID | 确保被引用行的主键确实存在 |
Duplicate entry 'xxx' for key 'email' |
UNIQUE 约束冲突 | 改用不重复的值,或确认是否真的只是想 UPDATE |
Column count doesn't match value count |
INSERT 时列数和值数不一致 | 检查 VALUES 里是不是少写了或多写了 |
总结
本文从零开始,用"学生选课系统"这一贯穿全系列的实例,带你完成了 MySQL 入门的第一步:
- 数据库是专门管理数据的独立程序,解决了文件存储无法处理的并发、一致性、查询效率问题
- 关系型数据库的核心模型是表 ,表与表之间通过主键和外键建立关系
INT、VARCHAR、DATETIME、DECIMAL覆盖了日常开发 80% 的数据类型需求CREATE TABLE定义结构,INSERT写入数据,SELECT查询数据,UPDATE和DELETE修改和删除------WHERE 子句是后两者的保命符NULL用IS NULL判断,别用=JOIN是多表查询的核心机制,下一篇文章将深入展开
掌握了本文的内容,你已经能独立完成"建库建表 → 插入数据 → 条件查询 → 简单聚合统计 → 两表联查"的完整流程。这些是后续所有进阶知识------索引、事务、ORM------的地基。
下一篇文章将深入 SQL 查询:多表 JOIN 的各种姿势、子查询的嵌套逻辑、内置函数的实战用法,以及如何用一条 SQL 完成之前需要多次查询才能实现的分析任务。同一个"学生选课系统",你将能用复杂查询挖掘出更多信息。