本教程面向大学一年级学生,按"基础概念 → SQL 语句 → 查询 → 多表 → 数据库设计 → MySQL 实践 → 索引事务 → 数据库编程 → 综合项目"的路线展开。重点突出SELECT 查询、JOIN 多表查询、数据库设计、索引优化—— 这是每个开发者的核心技能。
在动手写 SQL 之前,先建立对"数据库"和"SQL"的基本认知。
数据库(Database)是长期存储在计算机内的、有组织的、可共享的数据集合。
一句话总结:数据库是几乎所有软件系统的核心 —— 学好 SQL,就是掌握打开数据世界大门的钥匙。
SQL(Structured Query Language)是用于操作关系数据库的标准编程语言。
本节介绍数据库中最常用的"词汇" —— 理解它们能让你后续学习事半功倍。
| 术语 | 含义 | 类比 |
|---|---|---|
| 数据库 (Database) | 一组相关表的集合 | 一个 Excel 工作簿 |
| 表 (Table) | 二维数据结构,行 + 列 | 工作簿中一个 sheet |
| 行 (Row) / 记录 (Record) | 表中的一行数据 | Excel 一行 |
| 列 (Column) / 字段 (Field) | 表中的一列 | Excel 一列 |
| 主键 (Primary Key) | 唯一标识一行 | 学号、身份证号 |
| 外键 (Foreign Key) | 关联另一张表的主键 | 成绩表的"学号" |
| 类别 | 类型 | 说明 |
|---|---|---|
| 数值 | INT, BIGINT, DECIMAL, FLOAT, DOUBLE | 整数 / 小数 |
| 字符串 | CHAR, VARCHAR, TEXT | CHAR 定长、VARCHAR 变长 |
| 日期时间 | DATE, TIME, DATETIME, TIMESTAMP | YYYY-MM-DD 等 |
| 布尔 | BOOLEAN / TINYINT(1) | 真 / 假 |
| 特殊 | NULL | "无值",与 0 / '' 不同 |
INT AUTO_INCREMENT PRIMARY KEY,由数据库自动生成。SQL 语句按功能可分为五类。掌握分类后,看到任何 SQL 都能立刻知道它的"作用"。
| 类型 | 含义 | 主要语句 | 举例 |
|---|---|---|---|
| DDL | 数据定义语言 | CREATE / ALTER / DROP | 建表、改表、删表 |
| DML | 数据操作语言 | INSERT / UPDATE / DELETE | 增、改、删数据 |
| DQL | 数据查询语言 | SELECT | 查询数据 |
| DCL | 数据控制语言 | GRANT / REVOKE | 权限管理 |
| TCL | 事务控制语言 | COMMIT / ROLLBACK | 事务提交与回滚 |
学习优先级:大一阶段重点掌握 DQL(SELECT)、DML(CRUD)、DDL —— 这三类覆盖了 95% 的日常工作。
数据库 = 多个表的"容器"。本节学如何创建、查看、使用、删除数据库。
-- 创建数据库(指定字符集)
CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4;
-- 查看所有数据库
SHOW DATABASES;
-- 切换到目标数据库
USE school;
-- 删除数据库(⚠️ 高危操作)
DROP DATABASE school;表是数据库的核心。本节学 CREATE / ALTER / DROP 表。
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT CHECK (age >= 0 AND age <= 150),
gender VARCHAR(10) DEFAULT '未知',
email VARCHAR(100) UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 查看表结构
DESC student;
-- 查看建表 SQL
SHOW CREATE TABLE student;student_name);表名复数(students、orders);避免 SQL 关键字。-- 添加列
ALTER TABLE student ADD phone VARCHAR(20);
-- 修改列类型
ALTER TABLE student MODIFY name VARCHAR(100);
-- 重命名列
ALTER TABLE student CHANGE phone mobile VARCHAR(20);
-- 删除列
ALTER TABLE student DROP COLUMN mobile;
-- 重命名表
ALTER TABLE student RENAME TO students;
-- 删除表
DROP TABLE student;DML 三大操作之一:增。
-- 单条插入(推荐:指定字段名)
INSERT INTO student (name, age, gender)
VALUES ('张三', 18, '男');
-- 多条插入
INSERT INTO student (name, age, gender)
VALUES
('李四', 19, '女'),
('王五', 20, '男'),
('赵六', 21, '女');
-- 从另一张表复制
INSERT INTO student_backup
SELECT * FROM student WHERE age >= 18;DML 第二大操作:改。UPDATE 必带 WHERE 是铁律。
-- ✔ 正确:带 WHERE 条件
UPDATE student
SET age = 19
WHERE id = 1;
-- 多字段更新
UPDATE student
SET age = 20, gender = '女'
WHERE name = '李四';
-- ❌ 灾难性错误:忘记 WHERE
-- UPDATE student SET age = 20;
-- → 会把所有学生的年龄都改成 20!SELECT ... WHERE ... 验证条件。DML 第三大操作:删。三种"删"的区别是高频考点。
| 操作 | 对象 | 回滚? | 自增? | 速度 |
|---|---|---|---|---|
| DELETE FROM t WHERE ... | 数据 | ✔ 可回滚 | 保留 | 慢 |
| TRUNCATE TABLE t | 数据 | ❌ 不能回滚 | 重置 | 快 |
| DROP TABLE t | 表 | ❌ 不能回滚 | 删除表本身 | 最快 |
-- ✔ 删除指定行
DELETE FROM student WHERE id = 1;
-- 慎用:清空整张表
TRUNCATE TABLE student;
-- 慎用:删除整张表
DROP TABLE student;UPDATE student SET age = 20 没带 WHERE 会怎样?SELECT 是 SQL 中最重要、使用频率最高的语句。整个数据分析、报表、业务功能都依赖它。
SELECT [DISTINCT] 列1, 列2 AS 别名, 聚合函数(列)
FROM 表名
WHERE 行过滤条件
GROUP BY 分组列
HAVING 分组后过滤
ORDER BY 排序列 [ASC | DESC]
LIMIT [偏移,] 行数;-- 查询所有列(生产环境慎用 *)
SELECT * FROM student;
-- 查询指定列(推荐:按需取列)
SELECT name, age FROM student;
-- AS 别名(可省略 AS)
SELECT name AS 姓名, age AS 年龄
FROM student;
-- DISTINCT 去重
SELECT DISTINCT gender FROM student;WHERE 是 SELECT 的"过滤器"—— 决定返回哪些行。
-- 比较
WHERE age >= 18
-- AND / OR / NOT
WHERE age >= 18 AND age <= 22
WHERE gender = '男' OR gender = '女'
WHERE NOT (age = 18)
-- BETWEEN:闭区间 [18, 22]
WHERE age BETWEEN 18 AND 22
-- IN:等于列表中任一值
WHERE id IN (1, 2, 3)
-- IS NULL / IS NOT NULL
WHERE email IS NULL
WHERE email IS NOT NULLNULL 不能用 = NULL 或 != NULL,必须用 IS NULL / IS NOT NULL。LIKE、IN、BETWEEN —— 让查询更灵活。
-- 两种通配符:
-- % 匹配任意数量字符(含 0 个)
-- _ 匹配恰好 1 个字符
SELECT * FROM student
WHERE name LIKE '张%'; -- 张三、张三丰、张飞(非姓张的不行)
SELECT * FROM student
WHERE name LIKE '张_'; -- 张三、张四(恰好 2 字)
SELECT * FROM student
WHERE email LIKE '%@gmail.com'; -- 以 @gmail.com 结尾
-- 包含"小"的:%小%
-- 第 2 个字是"小"的:_小%
-- 转义特殊字符(MySQL 默认 \)
WHERE name LIKE '50\%' ESCAPE '\'; -- 匹配 "50%"%abc 这类"前缀为通配符"的查询无法使用索引,会全表扫描。慎用。聚合函数把多行"压缩"成一个值;GROUP BY 把数据分组。
| 函数 | 作用 |
|---|---|
| COUNT(*) | 行数 |
| COUNT(col) | col 非 NULL 的行数 |
| SUM(col) | 求和 |
| AVG(col) | 平均 |
| MAX(col) / MIN(col) | 最大 / 最小 |
-- 总人数
SELECT COUNT(*) AS total FROM student;
-- 平均年龄
SELECT AVG(age) FROM student;
-- 按性别分组
SELECT gender, COUNT(*) AS cnt, AVG(age) AS avg_age
FROM student
GROUP BY gender;
-- 加上 WHERE 过滤(先过滤再分组)
SELECT class_id, AVG(score) AS avg_score
FROM score
WHERE score >= 60 -- 只看及格的
GROUP BY class_id;-- 各班平均分超过 80 分的班级
SELECT class_id, AVG(score) AS avg_score
FROM score
GROUP BY class_id
HAVING AVG(score) > 80; -- 分组后的过滤WHERE vs HAVING:WHERE 是分组前对原始行过滤;HAVING 是分组后对聚合结果过滤。
ORDER BY 让结果有序,LIMIT 实现分页 —— Web 应用的核心。
-- 单字段排序
SELECT * FROM student ORDER BY age ASC; -- 升序(默认)
SELECT * FROM student ORDER BY age DESC; -- 降序
-- 多字段排序:先按 age 降序,再按 id 升序
SELECT * FROM student ORDER BY age DESC, id ASC;
-- LIMIT:取前 N 条
SELECT * FROM student LIMIT 10;
-- LIMIT offset, count:分页(offset 从 0 开始)
-- 跳过前 10 条,取接下来的 10 条(第 2 页)
SELECT * FROM student ORDER BY id LIMIT 10, 10;
-- 分页公式:LIMIT (页码 - 1) * 每页, 每页JOIN 是 SQL 第二个核心 —— 把多张表"拼"起来。
-- INNER JOIN:只返回两表都匹配的行
SELECT s.id, s.name, c.class_name
FROM student s
INNER JOIN class c ON s.class_id = c.id;
-- LEFT JOIN:保留左表全部
SELECT s.id, s.name, c.class_name
FROM student s
LEFT JOIN class c ON s.class_id = c.id;
-- 三表 JOIN:查张三的课程成绩
SELECT s.name, co.course_name, sc.score
FROM student s
JOIN score sc ON s.id = sc.student_id
JOIN course co ON sc.course_id = co.id
WHERE s.name = '张三';ON 条件,否则返回两表行数相乘的"笛卡尔积",数据量爆炸。"查询中的查询" —— 把多个 SELECT 嵌套使用。
-- 1) WHERE 子查询:年龄大于平均年龄
SELECT * FROM student
WHERE age > (SELECT AVG(age) FROM student);
-- 2) IN 子查询:选修了"数据库"的学生
SELECT * FROM student
WHERE id IN (
SELECT student_id FROM score
WHERE course_id = (SELECT id FROM course WHERE name = '数据库')
);
-- 3) FROM 子查询:当作临时表
SELECT class_id, avg_score
FROM (SELECT class_id, AVG(score) AS avg_score FROM score GROUP BY class_id) AS t
WHERE avg_score > 80;
-- 4) EXISTS:是否存在
SELECT * FROM course c
WHERE EXISTS (SELECT 1 FROM score WHERE course_id = c.id);好的设计 = 减少数据冗余 + 避免更新异常。本节讲 ER 模型与三大范式。
CREATE TABLE student (
name VARCHAR(50),
class_name VARCHAR(50),
course_1 VARCHAR(50), score_1 INT,
course_2 VARCHAR(50), score_2 INT,
course_3 VARCHAR(50), score_3 INT,
-- 每多一门课就要改表结构
);CREATE TABLE student(id, name, class_id);
CREATE TABLE class(id, class_name);
CREATE TABLE course(id, course_name);
CREATE TABLE score(id, student_id, course_id, score);| 范式 | 要求 | 目的 |
|---|---|---|
| 1NF | 字段不可再分(原子性) | 每个字段只存一个值 |
| 2NF | 非主属性完全依赖主键 | 消除部分依赖 |
| 3NF | 非主属性不传递依赖主键 | 消除"学号 → 班级 → 班主任"的传递 |
约束 = 数据库保证数据合法性的内置机制。
| 约束 | 作用 |
|---|---|
| PRIMARY KEY | 主键:唯一 + 非空 |
| FOREIGN KEY | 外键:引用另一张表的主键 |
| UNIQUE | 唯一约束(可空,但非 NULL 值必须唯一) |
| NOT NULL | 非空约束 |
| DEFAULT | 默认值约束 |
| CHECK | 检查约束(MySQL 8.0.16+ 才真正生效) |
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
age INT CHECK (age >= 0 AND age <= 150),
gender VARCHAR(10) DEFAULT '未知',
class_id INT,
FOREIGN KEY (class_id) REFERENCES class(id)
ON DELETE CASCADE
);
-- 外键级联:删班级时自动删其学生(慎用)
-- ON DELETE CASCADE / SET NULL / RESTRICT / NO ACTION从安装到登录到第一个查询 —— 把 MySQL 用起来。
# macOS 安装
brew install mysql
# 启动服务
brew services start mysql
# Linux
sudo systemctl start mysql
# Windows:从官网下载 MSI 安装包
# 登录
mysql -u root -p
# 查看数据库
mysql> SHOW DATABASES;
# 退出
mysql> exit;| 类别 | 类型 |
|---|---|
| 整数 | TINYINT(1B) · SMALLINT(2B) · INT(4B) · BIGINT(8B) |
| 小数 | FLOAT · DOUBLE · DECIMAL(p,s)(精确) |
| 字符串 | CHAR(n) 定长 · VARCHAR(n) 变长 · TEXT · LONGTEXT |
| 日期 | DATE · TIME · DATETIME · TIMESTAMP(自动时区)· YEAR |
| 其他 | JSON(MySQL 5.7+)· ENUM · BLOB |
索引 = 数据库的"目录" —— 让查询从全表扫变为快速定位。
-- 创建索引(CREATE 时)
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
email VARCHAR(100),
UNIQUE KEY uk_email (email), -- 唯一索引
KEY idx_name (name) -- 普通索引
);
-- 事后添加 / 删除索引
CREATE INDEX idx_age ON student(age);
ALTER TABLE student ADD INDEX idx_class_name(class_name);
DROP INDEX idx_age ON student;
-- 联合索引(多字段索引,列顺序很重要!)
CREATE INDEX idx_name_age ON student(name, age);
-- 查看索引
SHOW INDEX FROM student;
-- EXPLAIN:分析查询是否用上了索引
EXPLAIN SELECT * FROM student WHERE name = '张三';LIKE '%abc%'(前缀通配符);WHERE func(col)(函数运算);OR 中部分条件没索引;类型不一致(如 name = 123)。事务 = 一组 SQL 要么全部成功,要么全部失败 —— 数据库正确性的基石。
| 特性 | 含义 |
|---|---|
| Atomicity 原子性 | 事务内操作要么全成功,要么全失败 |
| Consistency 一致性 | 事务前后数据满足所有约束 |
| Isolation 隔离性 | 并发事务互不干扰 |
| Durability 持久性 | 提交后永久保存,即使断电 |
START TRANSACTION; -- 或 BEGIN
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 假设这里程序崩溃 / 网络中断……
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 两步都成功
COMMIT;
-- 任一步失败:
-- ROLLBACK; -- 撤销所有改动START TRANSACTION。视图 = 一条 SELECT 查询的命名快照,可以像表一样查询。
-- 创建视图
CREATE VIEW v_student_score AS
SELECT s.id, s.name, co.course_name, sc.score
FROM student s
JOIN score sc ON s.id = sc.student_id
JOIN course co ON sc.course_id = co.id;
-- 查询视图(和查询表一样)
SELECT * FROM v_student_score WHERE score >= 60;
-- 修改视图
CREATE OR REPLACE VIEW v_student_score AS ...;
-- 删除视图
DROP VIEW v_student_score;视图的好处:① 简化复杂查询 ② 隐藏表结构(安全)③ 数据独立。
SQL 进阶 —— "分组但保留每行的细节"。MySQL 8.0+ / PostgreSQL / Oracle / SQL Server 全部支持。
-- 查每个班成绩前 3 名
SELECT * FROM (
SELECT
s.class_id,
s.name,
sc.score,
ROW_NUMBER() OVER (PARTITION BY s.class_id ORDER BY sc.score DESC) AS rk
FROM student s
JOIN score sc ON s.id = sc.student_id
) t
WHERE rk <= 3;
-- ROW_NUMBER():连续编号(1, 2, 3, 4)
-- RANK(): 同分同名,下一空位(1, 2, 2, 4)
-- DENSE_RANK(): 同分同名,下一连续(1, 2, 2, 3)
-- 每行的累计求和
SELECT name, score,
SUM(score) OVER (ORDER BY id) AS cumulative
FROM score;Python 通过 mysql-connector 或 PyMySQL 连接 MySQL。
import pymysql
# 1) 建立连接
conn = pymysql.connect(
host="localhost",
user="root",
password="123456",
database="school",
charset="utf8mb4",
)
# 2) 获取游标
with conn.cursor() as cur:
# 3) 执行 SQL(参数化防注入!)
cur.execute(
"SELECT * FROM student WHERE name = %s",
("张三",)
)
rows = cur.fetchall()
for row in rows:
print(row)
# 4) 插入 + 提交事务
cur.execute(
"INSERT INTO student(name, age) VALUES(%s, %s)",
("李四", 20)
)
conn.commit()
conn.close()SQL 注入是 Web 安全第一杀手 —— 必须掌握防范方法。
username = input("用户名:")
sql = f"SELECT * FROM user WHERE name = '{username}'"
cur.execute(sql)
# 如果用户输入:
# ' OR '1'='1
# SQL 变成:
# SELECT * FROM user WHERE name = '' OR '1'='1'
# → 返回所有用户!username = input("用户名:")
sql = "SELECT * FROM user WHERE name = %s"
cur.execute(sql, (username,))
# 数据库驱动会自动转义恶意字符3 个项目覆盖学生管理、图书管理、电商三大场景 —— 体现"前端 → 后端 → SQL → MySQL"完整链路。
CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4;
USE school;
CREATE TABLE class (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL
);
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
class_id INT,
FOREIGN KEY (class_id) REFERENCES class(id)
);
CREATE TABLE course (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL
);
CREATE TABLE score (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT,
course_id INT,
score DECIMAL(5,2),
FOREIGN KEY (student_id) REFERENCES student(id),
FOREIGN KEY (course_id) REFERENCES course(id)
);
-- 常用查询
-- 1. 查每个学生的总分、平均分
SELECT s.name, SUM(sc.score) AS total, AVG(sc.score) AS avg
FROM student s JOIN score sc ON s.id = sc.student_id
GROUP BY s.id, s.name;
-- 2. 查每门课的最高分、最低分、参考人数
SELECT c.name, MAX(sc.score), MIN(sc.score), COUNT(*)
FROM course c JOIN score sc ON c.id = sc.course_id
GROUP BY c.id;
-- 3. 各班平均分排名(窗口函数)
SELECT c.name, AVG(sc.score) AS avg_score,
RANK() OVER (ORDER BY AVG(sc.score) DESC) AS rk
FROM class c
JOIN student s ON c.id = s.class_id
JOIN score sc ON s.id = sc.student_id
GROUP BY c.id, c.name;含图书 / 读者 / 借阅记录 / 分类四张表。实现借书、还书、逾期查询。
用户、商品、订单、订单项、支付表。涉及一对多 + 多对多关系,需要用中间表(如收藏夹)实现。重点学习:JOIN + 事务 + 索引优化。
写 SQL 时随手翻一翻 —— 比每次去搜更快。
CREATE DATABASE db_name;
CREATE TABLE t (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL DEFAULT '',
age INT CHECK (age >= 0),
email VARCHAR(100) UNIQUE,
FOREIGN KEY (dept_id) REFERENCES dept(id)
);
ALTER TABLE t
ADD COLUMN phone VARCHAR(20),
DROP COLUMN phone,
MODIFY name VARCHAR(100),
RENAME TO new_t;
DROP TABLE t;
TRUNCATE TABLE t;INSERT INTO t (a, b) VALUES (1, 'x');
UPDATE t SET a = 2 WHERE id = 1;
DELETE FROM t WHERE a > 0;
SELECT col1, col2, COUNT(*), SUM(col3)
FROM t
WHERE col1 LIKE 'a%' AND col2 BETWEEN 1 AND 10
GROUP BY col1
HAVING COUNT(*) > 3
ORDER BY col2 DESC
LIMIT 10;| 类型 | 结果 |
|---|---|
| INNER JOIN | 两表都有匹配的行 |
| LEFT JOIN | 左表全部 + 右表匹配(无匹配则 NULL) |
| RIGHT JOIN | 右表全部 + 左表匹配 |
| CROSS JOIN | 笛卡尔积(慎用) |
| SELF JOIN | 自己连自己(解决"行内比较") |
UPDATE / DELETE 忘带 WHERE → 整表更新/删除SELECT * 在生产环境 → 浪费 IO、不稳定LIMIT 1000000, 10 → 性能灾难WHERE 过滤 聚合函数 → 应改为 HAVINGNULL = NULL 比较 → 永远为 NULL,应改为 IS NULL