My App

MySQL

1. SQL 是什么?

SQL(Structured Query Language) 是一门操作关系型数据库的标准编程语言,用来:

  • 定义数据库中的各种对象(数据库、表、字段、索引等)
  • 对表中的数据进行增删改查
  • 控制数据库用户和权限

一句话:只要是关系型数据库(MySQL、PostgreSQL、Oracle、SQL Server 等),都要讲 SQL,SQL 是它们的统一“交流语言标准”。

2. SQL 的四大分类

常见会把 SQL 按照“主要用途”拆成四大类,便于记忆:

分类全称说明
DDLData Definition Language数据定义语言,用来定义数据库对象(库、表、字段)
DMLData Manipulation Language数据操作语言,对表中的数据进行增删改
DQLData Query Language数据查询语言,从表中查询记录
DCLData Control Language数据控制语言,创建数据库用户、控制访问权限

快速联想:

  • DDL:结构相关(创建/修改/删除“库、表、字段”)
  • DML:数据相关(对记录做 INSERT / UPDATE / DELETE
  • DQL:查询相关(SELECT 为主)
  • DCL:权限相关(GRANT / REVOKE / CREATE USER 等)

下面先从最基础、最常用的 DDL - 数据库级操作 开始。

3. DDL - 数据库级操作

这些语句主要是针对“数据库本身”进行操作,比如查看有哪些库、创建/删除库等。

3.1 查看数据库

-- 查询所有数据库
SHOW DATABASES;

-- 查询当前正在使用的数据库
SELECT DATABASE();

3.2 切换数据库

-- 使用 / 切换到指定数据库
USE 数据库名;

小技巧:在命令行或客户端工具中,执行 USE xxx; 之后,再执行的所有 SQL 默认都作用在这个数据库上。

3.3 创建数据库

-- 创建数据库,如果已存在则不报错
CREATE DATABASE [IF NOT EXISTS] 数据库名
    [DEFAULT CHARSET utf8mb4];

说明:

  • IF NOT EXISTS:防止重复创建时报错
  • DEFAULT CHARSET:指定默认字符集(推荐 utf8mb4,支持 emoji)

3.4 删除数据库

-- 删除数据库,如果不存在则不报错
DROP DATABASE [IF EXISTS] 数据库名;

一定要谨慎执行 DROP DATABASE,生产环境删除库属于高危操作,一般需要额外确认或通过运维流程处理。

4. DDL - 表操作(查询、修改、删除)

进行查看、增删改字段、改表名、删表等,都用下面这些语句(需先 USE 数据库名; 进入对应库)。

4.1 查询表

-- 查询当前数据库的所有表
SHOW TABLES;

-- 查询表结构(字段名、类型、约束等)
DESC 表名;

-- 查询建表语句(完整 DDL)
SHOW CREATE TABLE 表名;

4.2 修改表(ALTER TABLE)

-- 添加字段
ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释] [约束];

-- 修改字段类型(不改字段名)
ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度);

-- 修改字段名与字段类型
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束];

-- 删除字段
ALTER TABLE 表名 DROP COLUMN 字段名;

-- 修改表名
ALTER TABLE 表名 RENAME TO 新表名;

4.3 删除表

-- 删除表,如果不存在则不报错
DROP TABLE [IF EXISTS] 表名;

生产环境慎用 DROP TABLE,删表前务必确认库、表名无误,必要时先备份。

5. 约束(Constraints)

建表或改表时,通过约束可以限制字段的取值,保证数据的正确性和一致性。常用约束如下:

约束描述关键字
非空约束限制该字段值不能为 nullNOT NULL
唯一约束保证字段的所有数据都是唯一、不重复的UNIQUE
主键约束主键是一行数据的唯一标识,要求非空且唯一PRIMARY KEY
默认约束保存数据时,如果未指定该字段值,则采用默认值DEFAULT
外键约束让两张表的数据建立连接,保证数据的一致性和完整性FOREIGN KEY

5.1 在建表中使用约束

CREATE TABLE user (
    id          INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    username    VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名,非空且唯一',
    gender      CHAR(1) DEFAULT '男' COMMENT '性别,默认男',
    dept_id     INT COMMENT '部门 id',
    CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES dept(id)  -- 外键:关联 dept 表
);
  • 主键id 作为一行数据的唯一标识,一般配合 AUTO_INCREMENT 自增。
  • 非空 + 唯一username 不能为 null,且不能重复。
  • 默认值:插入时未指定 gender 则使用 '男'
  • 外键dept_id 必须在 dept(id) 中存在,保证“用户所属部门”一定存在。

5.2 约束小结

  • 非空 / 唯一 / 默认:只影响本表当前字段。
  • 主键:一张表通常只有一个主键(或联合主键),用于唯一标识一行。
  • 外键:在应用层(如 Java 代码)做校验也很常见,是否在数据库建外键可根据项目规范选择。

5.3 约束相关常见问题

1. MySQL 数据库中分为哪几类约束,对应的关键字是什么?

常见分为五类,对应关系见上文「5. 约束」中的表格,速记如下:

约束类型关键字
非空约束NOT NULL
唯一约束UNIQUE
主键约束PRIMARY KEY
默认约束DEFAULT
外键约束FOREIGN KEY

2. 如何实现主键自增的效果?

在定义主键字段时加上 AUTO_INCREMENT,插入时可以不写该字段,由数据库自动按递增顺序分配值:

id INT PRIMARY KEY AUTO_INCREMENT
  • 一般只用于整数类型的主键(如 INTBIGINT)。
  • 每张表通常只有一个 AUTO_INCREMENT 列。
  • 插入时若指定了该列的值,则使用指定值;未指定或写 NULL 时才会自增。

3. 一个字段上是否可以添加多个约束?

可以。 一个字段可以同时加多种约束,只要语义不冲突即可。例如:

  • 非空 + 唯一username VARCHAR(50) NOT NULL UNIQUE(不能为空,且不能重复)
  • 主键 + 自增id INT PRIMARY KEY AUTO_INCREMENT
  • 非空 + 默认值gender CHAR(1) NOT NULL DEFAULT '男'

书写时多个约束用空格隔开即可,顺序一般不影响效果。

6. 数据类型

MySQL 中的数据类型有很多,主要分为三类:数值类型字符串类型日期时间类型。建表时根据业务选对类型,既能保证数据正确,又能节省存储空间。

6.1 数值类型

类型大小(byte)有符号(SIGNED)范围无符号(UNSIGNED)范围描述
tinyint1-128 ~ 1270 ~ 255小整数值
smallint2-32768 ~ 327670 ~ 65535大整数值
mediumint3-8388608 ~ 83886070 ~ 16777215大整数值
int4-2147483648 ~ 21474836470 ~ 4294967295大整数值
bigint8-2^63 ~ 2^63-10 ~ 2^64-1极大整数值
float4单精度浮点范围0 及正浮点范围单精度浮点数值
double8双精度浮点范围0 及正浮点范围双精度浮点数值
decimal可变取决于精度与小数位同上小数值(精度更高)

精度写法说明(浮点与小数):

  • float(5,2) / double(5,2) / decimal(5,2)5 表示整个数字长度(含小数位),2 表示小数位个数。例如可存 123.45
  • 金额、数量等需要精确计算时,推荐用 decimal,避免浮点误差。

选型原则:在满足业务的前提下,尽量选择占用空间小的类型。

场景示例推荐类型
年龄tinyint unsigned
主键 idint unsignedbigint unsigned

6.2 字符串类型

类型大小(字节)描述
char0-255定长字符串
varchar0-65535变长字符串
tinyblob0-255不超过 255 的二进制数据
tinytext0-255短文本字符串
blob0-65535二进制形式的长文本
text0-65535长文本数据
mediumblob/mediumtext0-约 16M中等长度二进制/文本
longblob/longtext0-约 4G极大二进制/文本数据

char 与 varchar 对比

类型空间占用优势劣势
char(n)固定占 n 个字符;存 "A" 也占 n 个性能略高浪费磁盘空间
varchar(n)最多 n 个字符;存 "A" 占 1 个,存 "ABC" 占 3 个节约磁盘空间性能略低

常见用法示例

  • 长度固定的字段(如身份证、手机号):用 char,例如 idcard char(18)phone char(11)
  • 长度不固定的字段(如用户名、简介):用 varchar,例如 username varchar(50)

6.3 日期时间类型

类型大小(byte)范围格式描述
date31000-01-01 ~ 9999-12-31YYYY-MM-DD日期值
time3-838:59:59 ~ 838:59:59HH:MM:SS时间值或持续时间
year11901 ~ 2155YYYY年份值
datetime81000-01-01 00:00:00 ~ 9999-12-31 23:59:59YYYY-MM-DD HH:MM:SS日期+时间
timestamp41970-01-01 00:00:01 ~ 2038-01-19 03:14:07YYYY-MM-DD HH:MM:SS日期+时间(时间戳)

常见用法示例

  • 只存日期:birthday date
  • 需要日期+时间(创建/更新时间等):operate_time datetimecreated_at timestamp
  • timestamp 受范围限制(约到 2038 年),超出范围或需更大区间时用 datetime

6.4 数据类型常见问题

1. 数值类型在定义时,后面加了 unsigned 关键字是什么意思?

  • unsigned 表示无符号类型,该字段只能取 0 及正数(不能存负数)。
  • 不加 时默认为 signed(有符号),可以取负数。
  • 例如:年龄、主键 id 等不会为负的字段,常用 tinyint unsignedint unsigned 等,既省空间又语义清晰。

2. char 与 varchar 的区别是什么?什么时候用 char,什么时候用 varchar?

  • 区别char定长字符串varchar变长字符串(详见上文 6.2 对比表)。
  • 长度固定的字段,建议用 char,例如:身份证号 char(18)、手机号 char(11)
  • 长度不固定的字段,建议用 varchar,例如:用户名 varchar(50)、姓名 varchar(20)

7. DML - 数据操作语言

DML 英文全称是 Data Manipulation Language(数据操作语言),用来对数据库中表的数据记录进行增、删、改操作。

三种基本操作:

  • 添加数据INSERT
  • 修改数据UPDATE
  • 删除数据DELETE

下面以一张示例表为例说明语法(表结构:idname 名称、age 年龄、gender 性别,示例数据:令狐冲 28 男、风清扬 68 男、东方不败 32 男)。

7.1 添加数据(INSERT)

-- 为表中所有字段按顺序赋值(值的顺序、个数需与表结构一致)
INSERT INTO 表名 VALUES (值1, 值2, 值3, ...);

-- 为指定字段赋值(未指定字段取默认值或 NULL)
INSERT INTO 表名 (字段1, 字段2, ...) VALUES (值1, 值2, ...);

-- 一次插入多行
INSERT INTO 表名 (字段1, 字段2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...);

示例(假设表名为 emp,字段为 id, name, age, gender):

INSERT INTO emp (id, name, age, gender) VALUES (1, '令狐冲', 28, '男');
INSERT INTO emp (id, name, age, gender) VALUES (2, '风清扬', 68, '男'), (3, '东方不败', 32, '男');

7.2 修改数据(UPDATE)

-- 更新满足条件的记录(不加 WHERE 会更新整张表,慎用)
UPDATE 表名 SET 字段1 =1, 字段2 =2, ... [WHERE 条件];

示例:

UPDATE emp SET age = 29 WHERE name = '令狐冲';
UPDATE emp SET gender = '女' WHERE id = 3;

7.3 删除数据(DELETE)

-- 删除满足条件的记录(不加 WHERE 会删除表中所有记录,慎用)
DELETE FROM 表名 [WHERE 条件];

示例:

DELETE FROM emp WHERE id = 3;
-- 删除表中全部记录(表结构仍在)
-- DELETE FROM emp;

注意UPDATEDELETE 务必带好 WHERE 条件,避免误改、误删整表数据。

8. DQL - 数据查询语言

DQL(Data Query Language,数据查询语言)用来查询表中记录,核心语句是 SELECT。子句书写顺序与对应内容速记如下(与教材/幻灯片一致):

子句对应内容
SELECT字段列表
FROM表名列表
WHERE条件列表
GROUP BY分组字段列表
HAVING分组后条件列表
ORDER BY排序字段列表
LIMIT分页参数

完整语法结构如下(书写顺序与执行顺序不同,需区分):

SELECT
    字段列表
FROM
    表名列表
WHERE
    条件列表
GROUP BY
    分组字段列表
HAVING
    分组后条件列表
ORDER BY
    排序字段列表
LIMIT
    分页参数

8.1 各子句说明

子句说明
SELECT要查询的字段(可 * 表示全部,或列名、表达式、别名,可配合 DISTINCT 去重)
FROM数据来源的表(可多表,逗号或 JOIN)
WHERE行级过滤条件(在分组、聚合之前过滤)
GROUP BY按哪些字段分组(常配合聚合函数使用)
HAVING分组后的过滤条件(在分组、聚合之后过滤)
ORDER BY排序字段及升序/降序(ASC / DESC)
LIMIT分页:起始下标、条数(或只写条数,从第 1 条起)

8.2 基本示例

-- 查询全部字段
SELECT * FROM emp;

-- 查询指定字段并起别名
SELECT id, name AS 姓名, age 年龄 FROM emp;

-- 去重查询:查询所有不重复的性别
SELECT DISTINCT gender FROM emp;

-- 使用 CASE 把 job 数字映射为中文职位名,并去重
SELECT DISTINCT
       CASE job
           WHEN 1 THEN '班主任'
           WHEN 2 THEN '讲师'
           WHEN 3 THEN '学工主管'
           WHEN 4 THEN '教研主管'
           WHEN 5 THEN '咨询师'
           ELSE '未知'
       END AS 职位
FROM emp;

-- 条件查询
SELECT * FROM emp WHERE age > 30;
SELECT * FROM emp WHERE name LIKE '令狐%' AND gender = '男';

-- 排序:按年龄降序
SELECT * FROM emp ORDER BY age DESC;

-- 分页:从第 0 条开始取 10 条(LIMIT 起始下标, 条数)
SELECT * FROM emp LIMIT 0, 10;
-- 等价写法:LIMIT 10 表示取前 10 条
SELECT * FROM emp LIMIT 10;

8.3 分组与 HAVING

-- 按性别分组,统计每组人数
SELECT gender, COUNT(*) AS 人数 FROM emp GROUP BY gender;

-- 分组后只保留人数大于 1 的组
SELECT gender, COUNT(*) AS 人数 FROM emp GROUP BY gender HAVING COUNT(*) > 1;

WHERE 与 HAVING 区别WHERE 在分组前过滤行,HAVING 在分组后过滤组(可对聚合结果做条件)。

8.4 书写顺序与执行顺序

书写顺序:SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
(先确定表、过滤行、分组、再选字段、排序、分页。)

8.5 多表查询与笛卡尔积

8.5.1 多表查询是什么?

多表查询:在一条 SELECT 语句中,同时从多张表中获取数据

  • 单表查询语法:
    SELECT 字段列表 FROM 表名;
  • 多表查询基础写法:
    SELECT 字段列表 FROM 表1, 表2;

例如:

-- 同时从 emp(员工表)和 dept(部门表)中查询
SELECT * FROM emp, dept;

如果不加任何关联条件,查询结果会包含 emp 表的每一行 × dept 表的每一行的所有组合

  • 假设 emp 有 30 行,dept 有 6 行,则结果共有 30 × 6 = 180 行
  • 这就是典型的 笛卡尔积 现象。

8.5.2 什么是笛卡尔积?

笛卡尔积(Cartesian Product)是数学概念:给定两个集合 A 和 B,笛卡尔积表示 A 中每个元素与 B 中每个元素组合 的所有可能。

在 SQL 多表查询中:

  • FROM 表1, 表2没有关联条件 时,会得到“表 1 所有行 × 表 2 所有行”的所有组合。
  • 其中大部分组合在业务上是无效的(比如员工和部门完全随机匹配)。

示意图:

笛卡尔积示意

8.5.3 消除无效的笛卡尔积

在实际开发中,我们通常只关心“有业务关联的行”,例如只保留“员工所属部门匹配”的数据:

-- 通过 WHERE 条件关联 emp.dept_id 与 dept.id,消除无效组合
SELECT
    e.id,
    e.name,
    d.name AS dept_name
FROM
    emp e, dept d
WHERE
    e.dept_id = d.id;
  • 只有满足 e.dept_id = d.id 的行组合会保留,相当于只保留“有部门的员工”这部分有效数据
  • 这种写法本质上是内连接,也可以用 JOIN ... ON ... 的语法来表达(后续连接查询小节再展开)。

示意图(只保留“能配对上的”组合):

消除无效的笛卡尔积

记忆:多表查询时,一定要加好关联条件,避免产生大量无意义的笛卡尔积。

8.6 多表查询的分类(总览)

多表查询大致可以分为两大类:

  1. 连接查询(JOIN)
    • 内连接:只保留两张表中能匹配上的行,可以理解为 A、B 的交集部分。
    • 外连接
      • 左外连接(LEFT JOIN):保留左表的全部行,右表能匹配上的显示数据,匹配不上的补 NULL
      • 右外连接(RIGHT JOIN):保留右表的全部行,左表能匹配上的显示数据,匹配不上的补 NULL
  2. 子查询(Subquery)
    • 在一条 SELECT / UPDATE / DELETE 语句的 某个位置再嵌套一条 SELECT,内层查询结果作为外层的条件或数据来源。
    • 例如:WHERE id IN (SELECT emp_id FROM …)FROM (SELECT ... ) t 等。

8.7 内连接(INNER JOIN)

8.7.1 概念

内连接查询:查询两表或多表中交集部分数据,即能在所有参与表中成功匹配上的行

在 SQL 中,内连接从语法上可以分为两种写法:

  • 隐式内连接(旧式写法)
  • 显式内连接(推荐写法)

8.7.2 隐式内连接语法

SELECT 字段列表
FROM1, 表2
WHERE 连接条件 ...;

示例(员工与部门):

SELECT
    e.id,
    e.name,
    d.name AS dept_name
FROM
    emp e, dept d
WHERE
    e.dept_id = d.id;

写在 FROM 后面的多个表,通过 WHERE 子句中的连接条件“配对”。只有满足条件的行组合会被保留,本质上就是内连接。

在多表查询中,通常会给表起别名来简化书写:

-- AS 可以省略,推荐为表起简短有意义的别名
SELECT
    e.id,
    e.name,
    d.name AS dept_name
FROM
    emp AS e, dept AS d
WHERE
    e.dept_id = d.id
    AND e.gender = 1
    AND e.salary > 8000;
  • 常见写法:表名 AS 别名表名 别名AS 关键字可以省略。
  • 注意:一旦为表起了别名,在本条 SQL 中就不能再使用原表名来限定字段,只能使用别名(例如只能写 e.name,不能再写 emp.name)。

8.7.3 显式内连接语法(推荐)

SELECT 字段列表
FROM1 [INNER] JOIN2 ON 连接条件 ...

和上面同样的查询,用显式内连接写法为:

SELECT
    e.id,
    e.name,
    d.name AS dept_name
FROM
    emp e
    INNER JOIN dept d ON e.dept_id = d.id;

INNER 关键字可以省略,JOIN 默认就是内连接:
emp e JOIN dept d ON e.dept_id = d.id 等价于 emp e INNER JOIN dept d ON e.dept_id = d.id

隐式内连接 vs 显式内连接:

  • 语义上都是内连接,得到的结果集相同。
  • 显式 JOIN ... ON ... 写法结构更清晰,方便后续扩展为左外连接 / 右外连接等,实际开发中更推荐使用。
  • 隐式写法把连接条件和筛选条件都堆在 WHERE,SQL 稍微复杂就容易漏写某个连接条件,一旦漏掉就会退化为笛卡尔积,排查困难。
  • 隐式写法对外连接支持也不友好,真正要写 LEFT / RIGHT JOIN 时,必须采用显式 JOIN ... ON ... 语法才安全、清晰。

8.8 外连接(OUTER JOIN)

8.8.1 概念

外连接用于在保留一张表的全部行的同时,关联另一张表中能匹配上的数据。常见分为两种:

  • 左外连接(LEFT [OUTER] JOIN)
  • 右外连接(RIGHT [OUTER] JOIN)

记忆:内连接取“交集”,外连接是在交集的基础上额外保留一边的全部行

8.8.2 左外连接(LEFT JOIN)

语法

SELECT 字段列表
FROM1 LEFT [OUTER] JOIN2 ON 连接条件 ...;
  • 左外连接可以理解为:查询 表 1(左表)的所有数据,其中能在表 2 中匹配上的,显示表 2 字段;匹配不上的,表 2 字段为 NULL

示例(查询所有员工及其所属部门,即便个别员工没有部门也要查出来):

SELECT
    e.id,
    e.name,
    d.name AS dept_name
FROM
    emp e
    LEFT JOIN dept d ON e.dept_id = d.id;

8.8.3 右外连接(RIGHT JOIN)

语法

SELECT 字段列表
FROM1 RIGHT [OUTER] JOIN2 ON 连接条件 ...;
  • 右外连接可以理解为:查询 表 2(右表)的所有数据,其中能在表 1 中匹配上的,显示表 1 字段;匹配不上的,表 1 字段为 NULL

示例(查询所有部门及其员工,即便部门下暂时没有员工也要查出来):

SELECT
    e.id,
    e.name,
    d.name AS dept_name
FROM
    emp e
    RIGHT JOIN dept d ON e.dept_id = d.id;

实战中,如果能调整书写顺序,通常只用 LEFT JOIN 就够了(把希望“全部保留”的那张表写在左边),这样 SQL 更统一。

8.9 子查询(Subquery)

8.9.1 什么是子查询?

在一条 SQL 语句中嵌套另一条 SELECT 语句,这种嵌套查询就叫做子查询(也称为“嵌套查询”)。

SELECT * FROM t1
WHERE column1 = (SELECT column1 FROM t2 WHERE ...);
  • 内层的 SELECT 称为子查询(subquery)。
  • 外层的语句可以是 SELECT / INSERT / UPDATE / DELETE 中的任意一种,最常见的是 SELECT

8.9.2 子查询的常见分类

根据子查询返回结果的形状,通常可以分为四类:

  1. 标量子查询:子查询返回单个值(一行一列)。
    • 常配合比较运算符使用,如:=、>、<、>=、<= 等。
  2. 列子查询:子查询返回一列多行
    • 常配合 IN / ANY / ALL 等关键字使用。
  3. 行子查询:子查询返回一行多列
    • 常配合多列比较使用,如:(col1, col2) = (SELECT col1, col2 FROM ...)
  4. 表子查询:子查询返回多行多列(一整张“临时表”)。
    • 常出现在 FROM (子查询) AS 别名 位置,把子查询结果当作一张临时表再去查询。

子查询的要点是:先把需求拆分成若干清晰的小步骤,先分别写出每一步对应的 SQL,再把这些 SQL 逐步“嵌套合并”成一条完整的子查询语句

8.9.3 典型示例:从拆分到合并

下面分别给出 4 类子查询的典型示例,按照“先拆解步骤 → 再合并为子查询”的思路展示。

A. 标量子查询示例
  1. 需求 1:查询最早入职的员工信息。

拆分步骤:

-- 第一步:先查出最早入职日期
SELECT MIN(entry_date)
FROM emp;

-- 第二步:再根据上一步结果,查对应员工
SELECT *
FROM emp
WHERE entry_date = '2000-01-01';

合并为一条标量子查询:

SELECT *
FROM emp
WHERE entry_date = (
    SELECT MIN(entry_date)
    FROM emp
);
  1. 需求 2:查询在“阮小五”入职之后入职的员工信息。

拆分步骤:

-- 第一步:查出“阮小五”的入职日期
SELECT entry_date
FROM emp
WHERE name = '阮小五';

-- 第二步:查出入职日期晚于上一步结果的员工
SELECT *
FROM emp
WHERE entry_date > '2015-01-01';  -- 示例日期

合并为一条标量子查询:

SELECT *
FROM emp
WHERE entry_date > (
    SELECT entry_date
    FROM emp
    WHERE name = '阮小五'
);
B. 列子查询示例

需求:查询“教研部”和“咨询部”的所有员工信息。

拆分步骤:

-- 第一步:查出这两个部门的 id
SELECT id
FROM dept
WHERE name = '教研部'
   OR name = '咨询部';

-- 第二步:用已知的部门 id 查员工
SELECT *
FROM emp
WHERE dept_id IN (3, 2);  -- 假设教研部 id=2,咨询部 id=3

合并为一条列子查询:

SELECT *
FROM emp
WHERE dept_id IN (
    SELECT id
    FROM dept
    WHERE name = '教研部'
       OR name = '咨询部'
);
C. 行子查询示例

需求:查询与“李忠”的薪资和职位都相同的员工信息。

拆分步骤:

-- 第一步:查出“李忠”的薪资和职位
SELECT salary, job
FROM emp
WHERE name = '李忠';

-- 第二步:用查到的组合值去匹配其它员工
SELECT *
FROM emp
WHERE (salary, job) = (5000, 5);  -- 示例值

合并为一条行子查询:

SELECT *
FROM emp
WHERE (salary, job) = (
    SELECT salary, job
    FROM emp
    WHERE name = '李忠'
);
D. 表子查询示例

需求:获取每个部门中薪资最高的员工信息

拆分步骤:

-- 第一步:先按部门统计每个部门的最高薪资
SELECT dept_id, MAX(salary) AS max_salary
FROM emp
GROUP BY dept_id;

-- 第二步:再用第一步结果和 emp 表做连接,取出完整员工信息
SELECT *
FROM emp e,
     (SELECT dept_id, MAX(salary) AS max_salary
      FROM emp
      GROUP BY dept_id) temp
WHERE e.dept_id = temp.dept_id
  AND e.salary = temp.max_salary;

这里 (SELECT dept_id, MAX(salary) ...) temp 就是一个表子查询,返回“多行多列”的临时表 temp,再与 emp 进行多表查询。

9. 事务(Transaction)

9.1 什么是事务?

事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有操作作为一个整体一起向系统提交或撤销:要么这些操作同时成功,要么同时失败

例如“添加员工”业务中包含两个操作:

  1. emp 表插入员工基本信息。
  2. emp_expr 表插入该员工的工作经历。

这两个操作应视为一个整体:要么都成功(提交),要么都失败(回滚),不能出现“只插了员工、没插工作经历”或反过来。

默认行为:MySQL 的事务是自动提交的,即执行一条 DML 语句后,MySQL 会立即隐式提交事务。若要手动控制“多步操作一起提交或一起回滚”,需要先开启事务,再在合适时机执行 提交回滚

9.2 事务控制的三个步骤

事务控制主要三步:

步骤说明常用 SQL
开启事务在这组操作执行之前开启,后续 DML 暂不提交START TRANSACTION; / BEGIN;
提交事务所有操作都执行成功时,将本事务的修改持久化COMMIT;
回滚事务本组操作中有任一步失败时,撤销本事务的修改ROLLBACK;

9.3 示例:添加员工业务的事务管理

将“添加员工 + 工作经历”放在同一事务中:

-- 开启事务
START TRANSACTION;
-- 或:BEGIN;

-- 1. 保存员工基本信息
INSERT INTO emp VALUES (39, 'Tom', '123456', '汤姆', 1, '13300001111', 1, 4000, '1.jpg', '2023-11-01', 1, NOW(), NOW());

-- 2. 保存员工的工作经历信息
INSERT INTO emp_expr(emp_id, begin, end, company, job)
VALUES (39, '2019-01-01', '2020-01-01', '百度', '开发'),
       (39, '2020-01-10', '2022-02-01', '阿里', '架构');

-- 全部成功则提交
COMMIT;

-- 若有任一步失败,则回滚(撤销本事务内的所有修改)
-- ROLLBACK;
  • 若两条 INSERT 都成功,执行 COMMIT 后数据真正写入数据库。
  • 若其中任一条失败(如主键冲突、外键约束等),应执行 ROLLBACK,本事务内的修改全部撤销。

9.4 常见事务场景

事务管理的场景非常多,例如:

  • 银行转账:A 账户扣款 + B 账户加款,必须同时成功或同时失败。
  • 下单扣减库存:创建订单 + 扣减商品库存,要么都成功,要么都回滚,避免超卖或订单与库存不一致。
  • 添加员工(如上):员工基本信息 + 工作经历等多表插入,作为一个业务单元用事务保证一致性。

9.5 事务四大特性

面试题:事务有哪些特性?

  • 原子性(Atomicity):事务是不可分割的最小单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

事务的四大特性简称为:ACID

事务四大特性

进一步理解

  • 原子性(Atomicity):事务包装的一组 SQL 是一个不可分割的工作单元,事务中的操作要么全部成功,要么全部失败。
  • 一致性(Consistency):一个事务完成之后数据都必须处于一致性状态。
    • 如果事务成功完成,那么数据库的所有变化将生效。
    • 如果事务执行出现错误,那么数据库的所有变化将会被回滚(撤销),返回到原始状态。
  • 隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发的事务之间要相互隔离。
    • 一个事务的成功或失败对其他事务没有影响。
  • 持久性(Durability):一个事务一旦被提交或回滚,它对数据库的改变将是永久性的,哪怕数据库发生异常,重启之后数据依然存在。

On this page