小白也能看懂的 SQL 零基础教程:从关系模型到事务全解

前置准备:统一案例表

classes 班级表

id name
1 一班
2 二班
3 三班
4 四班

students 学生表

id class_id name gender score
1 1 小明 M 90
2 1 小红 F 95
3 1 小军 M 88
4 1 小米 F 73
5 2 小白 F 81
6 2 小兵 M 55
7 2 小林 M 85
8 3 小新 F 91
9 3 小王 M 89
10 3 小丽 F 85

第一部分:关系模型核心三要素

关系数据库的本质是用二维表格存储数据,表和表之间通过主键、外键建立关联,靠索引提升查询效率。

1.1 主键

什么是主键

主键是一张表里唯一标识一条记录的字段,相当于数据的「身份证号」。

  • 一张表只能有一个主键,值绝对不重复
  • 主键一旦插入,最好永远不要修改
  • 主键字段不允许为 NULL

设计原则

不要用业务字段当主键 (比如身份证号、手机号,业务规则可能变更)。推荐用自增整数 id 作为主键,和业务完全无关,不会因为业务变化受影响。

代码示例

建表时定义主键(MySQL 语法):

sql 复制代码
CREATE TABLE students ( id BIGINT NOT NULL AUTO_INCREMENT, -- 自增主键 
    class_id BIGINT NOT NULL, 
    name VARCHAR(100) NOT NULL, 
    gender VARCHAR(1) NOT NULL, 
    score INT NOT NULL, 
    PRIMARY KEY (id) -- 声明id为主键 
);

补充:也支持「联合主键」------ 用多个字段共同作为主键,只要所有主键列组合起来不重复即可。但日常开发尽量不用,会增加表的复杂度。

1.2 外键

什么是外键

外键用来在两张表之间建立一对多 关联关系。比如一个班级对应多个学生,我们就在学生表加一个 class_id 字段,指向班级表的主键 id,这个 class_id 就是外键。

外键约束

我们可以给字段添加外键约束,让数据库强制保证数据有效性 ------ 比如班级表里没有 id=99 的班级,学生表里就插不进去 class_id=99 的学生。

创建外键约束:
sql 复制代码
ALTER TABLE students ADD CONSTRAINT fk_class_id -- 外键约束名,可自定义
FOREIGN KEY (class_id) -- 当前表的外键列
REFERENCES classes (id); -- 关联到目标表的主键
删除外键约束:
sql 复制代码
ALTER TABLE students 
DROP FOREIGN KEY fk_class_id;
延伸的三种表关系
  • 一对多:一个班级 → 多个学生(最常见)
  • 多对多:通过中间表实现,比如学生和课程,一个学生选多门课,一门课有多个学生
  • 一对一:一张表的一条记录对应另一张表的一条记录,常用来拆分冷热字段(比如用户基本信息表 + 用户详情表)

1.3 索引

什么是索引

索引是对表中一列或多列的值预排序的数据结构,作用就像书的目录 ------ 不用一页页翻完整本书,直接通过目录快速定位内容,大幅提升查询速度。

创建普通索引

如果经常按分数查询学生,就给 score 列创建索引:

sql 复制代码
ALTER TABLE students 
ADD INDEX idx_score (score); -- idx_score是索引名,可自定义

也可以创建多列联合索引:

sql 复制代码
ALTER TABLE students 
ADD INDEX idx_name_score (name, score);

索引的优缺点

  • 优点:大幅提升查询速度
  • 缺点:插入、更新、删除数据时,需要同步修改索引,写入速度会变慢;索引越多,写入性能越低

唯一索引

如果某一列业务上要求唯一(比如邮箱、身份证号),但又不适合当主键,可以加唯一索引:

sql 复制代码
ALTER TABLE students 
ADD UNIQUE INDEX uni_name (name); -- 保证学生姓名不重复

也可以只加唯一约束、不创建索引:

sql 复制代码
ALTER TABLE students 
ADD CONSTRAINT uni_name UNIQUE (name);

小技巧:主键会自动创建主键索引,而且查询效率是最高的,因为主键保证绝对唯一。

第二部分:查询数据(开发最常用)

查询是 SQL 的核心,也是日常开发最高频的操作,我们从最简单到复杂逐层讲解。

2.1 基本查询

查询整张表的所有行、所有列:

sql 复制代码
SELECT * FROM students;

执行后会返回 students 表全部 10 条学生数据。

小知识:SELECT 后面可以跟表达式,比如 SELECT 100+200;,常用来测试数据库连接是否正常。

2.2 条件查询

WHERE 子句筛选符合条件的记录,比如只查 80 分以上的学生。

语法:
sql 复制代码
SELECT * FROM 表名 WHERE 条件表达式;

三种逻辑条件

  1. AND(并且) :同时满足两个条件示例:查询分数≥80 且是男生的学生
sql 复制代码
SELECT * FROM students WHERE score >= 80 AND gender = 'M';
  1. OR(或者) :满足任意一个条件即可示例:查询分数≥80 或者是男生的学生
sql 复制代码
SELECT * FROM students WHERE score >= 80 OR gender = 'M';
  1. NOT(取反) :不符合条件的记录示例:查询不是 2 班的学生
sql 复制代码
SELECT * FROM students WHERE NOT class_id = 2; -- 等价于 SELECT * FROM students WHERE class_id <> 2;

常用条件运算符

运算符 说明 示例
= 相等 name = '小明'
> / >= 大于 / 大于等于 score > 80
< / <= 小于 / 小于等于 score < 60
<> 不等于 gender <> 'M'
LIKE 模糊匹配 name LIKE '小%'(匹配所有姓「小」的学生)
优先级:NOT > AND > OR,拿不准的时候直接加小括号,不会出错。

2.3 投影查询

如果不需要所有列,只返回指定字段,就用投影查询,还可以给列起别名。

示例 1:只查 id、姓名、分数三列

sql 复制代码
SELECT id, name, score FROM students;

示例 2:给列起别名把 score 重命名为 points 返回:

sql 复制代码
SELECT id, name, score points FROM students;

示例 3:投影 + 条件组合只查询男生的 id、姓名和分数:

sql 复制代码
SELECT id, name, score FROM students WHERE gender = 'M';

2.4 排序

查询结果默认按主键排序,用 ORDER BY 可以指定按某一列排序。

示例 1:按分数从低到高升序

sql 复制代码
SELECT id, name, score FROM students ORDER BY score; 
-- ASC是升序,默认可以省略,等价于 ORDER BY score ASC

示例 2:按分数从高到低降序

sql 复制代码
SELECT id, name, score FROM students ORDER BY score DESC;

示例 3:多列排序先按分数倒序,分数相同的再按性别排序:

sql 复制代码
SELECT id, name, gender, score 
FROM students 
ORDER BY score DESC, gender;

示例 4:带条件的排序查询一班的学生,按分数倒序:

sql 复制代码
SELECT id, name, score 
FROM students 
WHERE class_id = 1 ORDER BY score DESC;

2.5 分页查询

数据量大的时候,一次返回所有数据太耗性能,分页每次只返回一页的数据。

语法:

sql 复制代码
LIMIT 每页条数 OFFSET 起始位置
  • 起始位置从 0 开始计算
  • 公式:第 N 页的 OFFSET = 每页条数 × (N - 1)

示例:每页 3 条,查询第 1、2、3 页

第 1 页:

sql 复制代码
SELECT id, name, score 
FROM students 
ORDER BY score DESC 
LIMIT 3 OFFSET 0;

第 2 页:

sql 复制代码
SELECT id, name, score 
FROM students 
ORDER BY score DESC 
LIMIT 3 OFFSET 3;

第 3 页:

sql 复制代码
SELECT id, name, score 
FROM students 
ORDER BY score DESC 
LIMIT 3 OFFSET 6;

MySQL 简写:LIMIT 3 OFFSET 3 可以简写成 LIMIT 3, 3

注意:OFFSET 越靠后,查询效率越低,大数据量分页需要专门优化。

2.6 聚合查询

用来做统计计算,比如统计总人数、平均分、最高分,不用把数据全查出来自己算。

常用聚合函数

函数 作用
COUNT(*) 统计行数
SUM(列名) 计算某一列的数值总和
AVG(列名) 计算某一列的平均值
MAX(列名) 求某一列的最大值
MIN(列名) 求某一列的最小值

示例 1:统计学生总人数

sql 复制代码
SELECT COUNT(*) num FROM students; 
-- 给结果列起个别名num,方便程序读取

返回结果:num = 10

示例 2:统计男生的平均成绩

sql 复制代码
SELECT AVG(score) average FROM students WHERE gender = 'M';

分组聚合

按某一列分组统计,比如统计每个班有多少学生:

sql 复制代码
SELECT class_id, COUNT(*) num 
FROM students 
GROUP BY class_id;

结果会返回 3 行,分别对应 1 班、2 班、3 班的人数。

进阶:按班级 + 性别分组,统计各班男女生人数

sql 复制代码
SELECT class_id, gender, COUNT(*) num 
FROM students 
GROUP BY class_id, gender;

2.7 多表查询

同时查询多张表,返回的是两张表的笛卡尔积------ 第一张表的每一行和第二张表的每一行两两组合。

基础示例:

sql 复制代码
SELECT * FROM students, classes;

学生表 10 行,班级表 4 行,结果会返回 10×4=40 行数据,大部分组合是没有意义的,实际开发中一定会加条件筛选,并且给表起别名,避免列名重复。

带条件和别名的多表查询:

sql 复制代码
SELECT 
    s.id sid, s.name, s.score, 
    c.id cid, c.name cname 
FROM students s, classes c 
WHERE s.class_id = c.id; -- 只保留班级id匹配的有效行

2.8 连接查询

连接查询是更规范的多表查询方式:先确定一张主表,再把另一张表的行按条件「拼接」上来。

INNER JOIN(内连接,最常用)

只返回两张表都匹配的行。

示例:查询学生信息,同时返回对应的班级名称

sql 复制代码
SELECT 
    s.id, s.name, s.class_id, 
    c.name class_name, s.score 
FROM students s 
INNER JOIN classes c 
ON s.class_id = c.id; -- 连接条件

结果:四班没有学生,所以不会出现在结果里。

LEFT JOIN(左连接)

左表的所有行都会返回,右表没有匹配的字段就填 NULL。

sql 复制代码
SELECT 
    s.id, s.name, c.name class_name 
FROM students s 
LEFT JOIN classes c 
ON s.class_id = c.id;

如果有学生的class_id在班级表不存在,class_name会显示 NULL。

RIGHT JOIN(右连接)

右表的所有行都会返回,左表没有匹配的字段就填 NULL。

sql 复制代码
SELECT 
    s.id, s.name, c.name class_name 
FROM students s 
RIGHT JOIN classes c 
ON s.class_id = c.id;

结果里会出现「四班」这一行,学生相关的列都是 NULL。

FULL JOIN(全连接)

左右表的所有行都返回,两边不匹配的都填 NULL,MySQL 原生不直接支持。

记忆口诀:

  • INNER JOIN:两边都有才返回
  • LEFT JOIN:左表全返回,右表匹配不上就空着
  • RIGHT JOIN:右表全返回,左表匹配不上就空着

第三部分:修改数据(增删改)

数据库的增删改查简称 CRUD,对应 Create、Retrieve、Update、Delete。

3.1 插入数据

语法:

sql 复制代码
INSERT INTO 表名 (字段1, 字段2, ...) VALUES (值1, 值2, ...);

示例:插入一条学生记录

sql 复制代码
INSERT INTO students (class_id, name, gender, score) VALUES (2, '大牛', 'M', 80);
  • 字段顺序不用和表结构完全一致,但值的顺序必须和字段一一对应
  • 自增主键 id 不用写,数据库会自动生成

批量插入

一条语句插入多条记录,用逗号分隔:

sql 复制代码
INSERT INTO students (class_id, name, gender, score) 
VALUES 
    (1, '大宝', 'M', 87), 
    (2, '二宝', 'M', 81), 
    (3, '三宝', 'M', 83);

3.2 更新数据

语法:

sql 复制代码
UPDATE 表名 SET 字段1=值1, 字段2=值2 WHERE 条件;

示例 1:更新指定学生的信息

sql 复制代码
UPDATE students SET name='大牛', score=66 WHERE id=1;

示例 2:批量更新多条记录给所有 80 分以下的同学加 10 分:

sql 复制代码
UPDATE students SET score = score + 10 WHERE score < 80;

⚠️ 高危提醒 UPDATE 如果不带 WHERE 条件,会更新整张表的所有数据:

sql 复制代码
UPDATE students SET score = 60; -- 危险操作!所有学生分数都变成60

执行前一定要先用 SELECT 测试 WHERE 条件是否正确。

3.3 删除数据

语法:

sql 复制代码
DELETE FROM 表名 WHERE 条件;

示例 1:删除指定学生

sql 复制代码
DELETE FROM students WHERE id=1;

示例 2:批量删除删除所有不及格的学生:

sql 复制代码
DELETE FROM students WHERE score < 60;

⚠️ 高危提醒 不带 WHERE 的 DELETE 会清空整张表:

sql 复制代码
DELETE FROM students; -- 危险操作!清空学生表所有数据

同样建议先 SELECT 确认条件再执行。

第四部分:实用 SQL 技巧与事务

4.1 实用 SQL 语句

这几个是开发中非常好用的语法糖,能帮你少写很多业务代码。

1. 插入或替换

如果记录存在就先删再插,不存在就直接插入,不用先查一遍:

sql 复制代码
REPLACE INTO students (id, class_id, name, gender, score) 
VALUES (1, 1, '小明', 'F', 99);

2. 插入或更新

如果记录存在就更新指定字段,不存在就插入:

sql 复制代码
INSERT INTO students (id, class_id, name, gender, score) 
VALUES (1, 1, '小明', 'F', 99) 
ON DUPLICATE KEY UPDATE name='小明', gender='F', score=99;

3. 插入或忽略

如果记录存在就啥也不做,不存在才插入:

sql 复制代码
INSERT IGNORE INTO students (id, class_id, name, gender, score) 
VALUES (1, 1, '小明', 'F', 99);

4. 复制表快照

把一张表的部分数据复制成一张新表,不用提前建表:

sql 复制代码
-- 把1班的学生复制成新表 students_of_class1 
CREATE TABLE students_of_class1 
SELECT * FROM students WHERE class_id = 1;

5. 查询结果直接写入表

统计结果直接存入统计表,不用程序中转:

sql 复制代码
-- 先建统计表 
CREATE TABLE statistics ( 
    id BIGINT NOT NULL AUTO_INCREMENT, 
    class_id BIGINT NOT NULL, 
    average DOUBLE NOT NULL, 
    PRIMARY KEY (id) ); 
-- 把各班平均分直接写入 
INSERT INTO statistics (class_id, average) 
SELECT class_id, AVG(score) 
FROM students 
GROUP BY class_id;

6. 强制使用指定索引

数据库查询优化器选错索引时,可以强制它使用指定索引:

sql 复制代码
SELECT * FROM students 
FORCE INDEX (idx_class_id) 
WHERE class_id = 1 
ORDER BY id DESC;

7. 什么是事务

事务是把多条 SQL 语句绑成一个整体,要么全部执行成功,要么全部失败,不会出现一半成功一半失败的情况。

最经典的场景是转账:A 账户扣 100 元,B 账户加 100 元,这两条必须同时成功,或者同时回滚。

事务四大特性(ACID)
  • A 原子性:所有 SQL 是一个原子单元,要么全执行,要么全不执行
  • C 一致性:事务结束后,数据状态保持一致(扣的钱一定到对方账户)
  • I 隔离性:多个事务并发执行,互相之间数据隔离
  • D 持久性:事务提交后,修改永久保存,断电不丢失

事务基础语法

正常提交:

sql 复制代码
BEGIN; -- 开启事务 
UPDATE accounts SET balance = balance - 100 WHERE id = 1; 
UPDATE accounts SET balance = balance + 100 WHERE id = 2; 
COMMIT; -- 提交事务,所有修改生效

出错回滚:

sql 复制代码
BEGIN; -- 执行SQL... 
ROLLBACK; -- 回滚,所有修改都撤销

8. 事务隔离级别

多个事务并发运行时,可能会出现脏读、不可重复读、幻读三类问题,数据库提供了 4 种隔离级别来平衡安全性和性能。

先明确三个概念:

  • 脏读 :一个事务读到了另一个事务还没提交的修改
  • 不可重复读:同一个事务内,两次读同一条记录,结果不一样
  • 幻读:同一个事务内,第一次查某条记录不存在,更新却成功了,再查又出现了
四种隔离级别对比:隔离级别 脏读 不可重复读 幻读
Read Uncommitted
Read Committed 不会
Repeatable Read 不会 不会
Serializable 不会 不会 不会
8.1 Read Uncommitted(读未提交)

最低隔离级别,会出现脏读。

  • 事务 A 修改了数据但未提交
  • 事务 B 读到了这个未提交的修改
  • 事务 A 回滚,事务 B 刚才读到的就是无效的「脏数据」

实际开发基本不用,安全性太差。

8.2 Read Committed(读已提交)

只能读到其他事务已经提交的数据,解决了脏读,但仍存在不可重复读。

  • 事务 B 第一次读数据,值是 A
  • 事务 A 修改数据并提交
  • 事务 B 第二次读同一条数据,值变成了 B
  • 同一个事务内,两次读取结果不一致

很多商业数据库的默认隔离级别是这个。

8.3 Repeatable Read(可重复读)

解决了脏读和不可重复读:同一个事务内,多次读取同一条记录,结果始终一致,哪怕别的事务修改并提交了,它也读不到。

但仍存在幻读

  • 事务 B 查询 id=99 的学生,结果为空
  • 事务 A 插入了 id=99 的学生并提交
  • 事务 B 再查还是空,但更新这条记录却成功了
  • 再次查询记录又出现了,像幻觉一样

MySQL InnoDB 引擎的默认隔离级别就是 Repeatable Read。

8.4 Serializable(串行化)

最高隔离级别,所有事务排队串行执行,脏读、不可重复读、幻读都不会出现。缺点是并发性能极差,只有对数据一致性要求极高的场景才会使用。

文末总结

  • 关系模型的核心是主键、外键、索引,分别负责唯一标识、表关联、提速查询。
  • 查询是 SQL 的重中之重,从基础查询到连接查询,层层递进,日常开发覆盖 90% 场景。
  • 增删改操作一定要注意带 WHERE 条件,避免全表误操作。
  • 事务保证数据一致性,四个隔离级别各有取舍,默认级别能满足绝大多数业务需求。
相关推荐
爱吃火鸡面呀1 小时前
MySQL 查询与函数详解:从基础条件筛选到高级字符处理
数据库·mysql
知行产研1 小时前
中国矿山无人驾驶出海的三重门
数据库·mysql·php
爱和冰阔落2 小时前
【MySQL 慢查询排查实战】列表接口逐渐变慢时,怎样从请求链路定位原因
android·数据库·mysql
Dxy12393102163 小时前
MySQL有哪些锁?InnoDB锁体系完整梳理博客
数据库·mysql
wear工程师3 小时前
MySQL 索引条件下推是什么?Using index condition 并不是覆盖索引
sql·mysql
方便面不加香菜4 小时前
MySQL 复合查询
数据库·mysql
花花鱼4 小时前
MySQL Illegal mix of collations 全解|字符集与排序规则冲突原理、分层排错、通用解决方案(适配5.7/8.0迁移)
数据库·mysql
Lyyaoo.13 小时前
Spring Boot Validation 声明式参数校验
spring boot·后端·mysql
南城以南溫暖如初14715 小时前
外卖CPS实战指南:从技术选型到运营落地全流程解析
java·spring boot·mysql·架构·vue
冰之杍20 小时前
MySQL utf8mb3 → utf8mb4 完整修改方案
数据库·mysql