小白也能看懂的 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 条件,避免全表误操作。
  • 事务保证数据一致性,四个隔离级别各有取舍,默认级别能满足绝大多数业务需求。
相关推荐
ClouGence3 小时前
MySQL 到 StarRocks 数据迁移同步:同步方案与实践指南
数据库·mysql
田超凡5 小时前
深入理解MySQL_10 Explain的用法
mysql·java-ee
cyforkk7 小时前
MySQL锁机制详解:从乐观锁到悲观锁
数据库·mysql
ShiXZ2137 小时前
指令集-SQL 常用指令速查手册
数据库·sql·mysql·oracle
郑州光合科技余经理7 小时前
海外外卖平台源码改造实战——多语言核心代码实现
java·开发语言·前端·后端·mysql·架构·php
不会写DN7 小时前
MySQL 并发插入竞态问题:原子写入实践指南
数据库·mysql
少时登峰8 小时前
【吃透 MySQL InnoDB连载】第 6 章・索引底层原理
数据库·mysql
许彰午9 小时前
从Oracle看MySQL聚簇索引的架构性缺陷与缓解之道
mysql·oracle·架构
AllData公司负责人9 小时前
数据集成管理|AIIData数据中台实现MySQL、Hive、Oracle一键接入Doris
大数据·数据库·人工智能·hive·mysql·oracle·数据分析