我们在 MySQL 基础篇中已经了解了库与表的定义操作,本篇详细讲解表数据的增删改查(CRUD),以及条件筛选、排序分页、聚合分组等核心查询能力,并梳理 SQL 执行的底层逻辑。
(一)增:INSERT 数据插入
1. 基础插入语法
INSERT [INTO] 表名 [(字段1, 字段2, ...)]
VALUES (值1, 值2, ...), (值1, 值2, ...), ...;
- 两种插入模式:
- 全列插入:省略字段列表,VALUES 必须按表定义的字段顺序传入所有值
- 指定列插入:明确写出字段名,VALUES 与字段一一对应,未指定字段使用默认值或 NULL
- 支持单行插入 和多行批量插入;批量插入减少网络交互和 IO 次数,性能远高于逐行插入,推荐批量操作。
示例:
-- 单行指定列插入
INSERT INTO test (ename) VALUES ('孙权');
-- 多行批量插入
INSERT INTO test (ename) VALUES ('刘备'), ('曹操'), ('关羽');
2. 主键 / 唯一键冲突的原子解决方案
插入数据时,如果表存在主键或唯一键,重复插入会直接报错。针对 "存在则更新、不存在则插入" 的业务场景,MySQL 提供两种原子方案,替代低效的 "先查询再插入" 试错逻辑。
方案 1:ON DUPLICATE KEY UPDATE(生产推荐)
作用 :插入时如果遇到主键 / 唯一键冲突,就执行UPDATE子句,更新冲突行的指定字段;如果无冲突,就正常插入。
INSERT [INTO] 表名 [(字段列表)]
VALUES (值列表)
ON DUPLICATE KEY UPDATE 字段1 = 新值1, 字段2 = 新值2, ...;
示例:
-- id是唯一键,插入'孙权'冲突时,将冲突行的ename更新为'孙权'
INSERT INTO test (id,ename) VALUES (1,'孙权'),(2,'刘备'),(3,'苏烈')
ON DUPLICATE KEY UPDATE ename = VALUES(ename);
--- id发生冲突使用孙权/刘备/苏烈进行替换
关键说明:
- 支持多行批量插入,每一行独立判断冲突、独立执行更新,不会互相影响
- 可以用
VALUES(字段名)引用本次插入的值,例如:ON DUPLICATE KEY UPDATE age = VALUES(age) + 1 - 是原子操作,并发安全,性能远高于 "先查后插",是业务中处理 "upsert" 场景的主流方案
方案 2:REPLACE INTO
作用 :插入时如果遇到主键 / 唯一键冲突,先删除旧行,再插入新行;如果无冲突,直接插入。
REPLACE [INTO] 表名 [(字段列表)]
VALUES (值列表), (值列表), ...;
示例:
REPLACE INTO test (id, ename) VALUES (10, 'd');
关键说明:
- 本质是「删除 + 插入」,未在语句中指定的字段,会被覆盖为表的默认值,会丢失原有数据
- 会触发 DELETE 和 INSERT 触发器,自增主键会重新生成
- 副作用较大,仅适合全字段覆盖的特殊场景,绝大多数场景推荐使用
ON DUPLICATE KEY UPDATE
(二)查:SELECT 数据查询
查询是 SQL 中最核心、最复杂的部分,我们按照逻辑执行顺序逐层拆解。
1. 基础投影:列的选择
(1)全列查询
SELECT * FROM 表名;
强烈不推荐生产环境使用
*:① 列越多,网络传输的数据量越大,查询性能下降
② 可能导致优化器无法选择合适的索引,出现索引失效
③ 表结构变更时,业务代码容易出现字段不兼容问题
(2)指定列查询
SELECT 字段1, 字段2, ... FROM 表名;
(3)表达式与计算列
SELECT 后不仅可以写字段,还可以写任意合法的表达式(常量、算术运算、函数调用等),生成计算列。 示例:
-- 10是常量表达式,作为计算列输出
SELECT id, 10 FROM test;
-- 字段运算 + 函数
SELECT id, salary * 12 AS annual_salary, UPPER(ename) FROM test;
(4)列别名
可以为字段或表达式设置别名,提升可读性,别名可用于后续的 ORDER BY 等子句。
SELECT id AS student_id, ename AS student_name FROM test;
-- AS关键字可以省略
SELECT id student_id, ename student_name FROM test;
2. 结果去重:DISTINCT
对查询结果的整行去重,所有列的值都相同时,只保留一行。
SELECT DISTINCT 字段1, 字段2, ... FROM 表名;
3. 条件筛选:WHERE
WHERE 子句在 FROM 之后执行,按条件过滤行,只有满足条件的行才会进入后续处理。
常用运算符与示例
| 运算符 | 说明 | 示例 |
|---|---|---|
>, >=, <, <= |
大于、大于等于、小于、小于等于 | SELECT id FROM test WHERE id > 10; |
= |
等于;不能判断 NULL ,NULL = NULL结果为 NULL |
SELECT * FROM test WHERE id = 1; |
<=> |
NULL 安全等于;NULL <=> NULL返回 TRUE (1) |
SELECT * FROM test WHERE name <=> NULL; |
!=, <> |
不等于 | SELECT * FROM test WHERE id != 5; |
BETWEEN a AND b |
范围匹配,闭区间[a,b] |
SELECT * FROM test WHERE id BETWEEN 1 AND 10; |
IN (值1, 值2, ...) |
匹配任意一个值 | SELECT * FROM test WHERE id IN (1,3,5); |
IS NULL |
判断为 NULL | SELECT * FROM test WHERE name IS NULL; |
IS NOT NULL |
判断不为 NULL | SELECT * FROM test WHERE name IS NOT NULL; |
LIKE |
模糊匹配;%匹配任意个字符,_匹配单个字符 |
SELECT * FROM test WHERE ename LIKE '孙%';``SELECT * FROM test WHERE ename LIKE '孙_'; |
NOT LIKE |
反向模糊匹配 | SELECT * FROM test WHERE ename NOT LIKE '孙%'; |
注意:不要对数值类型字段使用 LIKE 模糊查询,会发生隐式类型转换,导致索引失效。
重要规则:WHERE 中不能使用 SELECT 别名
因为 SQL 逻辑执行顺序中,WHERE 在 SELECT 之前执行,WHERE 执行时还没有生成列别名,因此下面的语句会报错:
-- 错误:WHERE无法识别student_id别名
SELECT id student_id FROM test WHERE student_id = 10;
4. 排序:ORDER BY
对 SELECT 输出的结果集按指定字段排序,在 SELECT 之后执行,因此可以使用 SELECT 的列别名。
-- 升序(默认,可省略ASC)
SELECT id FROM test WHERE id LIKE '1%' ORDER BY id ASC;
-- 降序
SELECT id FROM test ORDER BY id DESC;
- 支持多字段排序,例如
ORDER BY age DESC, id ASC:先按 age 降序,age 相同按 id 升序
5. 分页:LIMIT
截取最终结果的指定行数,避免大数据量返回,是分页查询的核心语法。
SELECT 字段 FROM 表名
WHERE 条件
ORDER BY 排序字段
LIMIT [偏移量,] 行数;
LIMIT N:取前 N 条LIMIT S, N:从第 S 条开始(从 0 计数),取 N 条
示例:
-- 取前5条
SELECT * FROM test LIMIT 5;
-- 从第10条开始,取5条(第3页,每页5条)
SELECT * FROM test ORDER BY id LIMIT 10, 5;
(三)改:UPDATE 数据更新
UPDATE 表名
SET 字段1 = 表达式1, 字段2 = 表达式2, ...
[WHERE 条件]
[ORDER BY 排序字段]
[LIMIT 行数];
- 执行顺序:先 WHERE 筛选行,再 ORDER BY 排序,最后按 LIMIT 限制行数,执行更新
- 注意 :UPDATE 的 LIMIT 仅支持
LIMIT N(限制更新行数),不支持带偏移量的写法 - 省略 WHERE 会更新全表所有数据,生产环境必须谨慎
示例:
-- 将id>10的行,按id降序,更新前2行的id+1
UPDATE test SET id = id + 1 WHERE id > 10 ORDER BY id DESC LIMIT 2;
(四)删:DELETE 数据删除
DELETE FROM 表名
[WHERE 条件]
[ORDER BY 排序字段]
[LIMIT 行数];
- 执行顺序与 UPDATE 一致:WHERE 筛选 → ORDER BY 排序 → LIMIT 限制 → 执行删除
- 同样,LIMIT 仅支持
LIMIT N,不支持偏移量 - 省略 WHERE 会删除全表所有行,但不会重置
auto_increment自增计数器 - DELETE 是 DML 语句,可通过事务回滚
示例:
-- 删除id>10的前2行
DELETE FROM test WHERE id > 10 ORDER BY id DESC LIMIT 2;
(五)清空表:TRUNCATE
TRUNCATE [TABLE] 表名;
- 本质:DDL 语句,直接删除表并重建一个结构相同的空表
- 特点:
- 执行速度极快,不逐行删除,直接释放表空间
- 会重置
auto_increment自增计数器 - 隐式提交事务,无法回滚
- 不会触发 DELETE 触发器
- 对比:
DELETE FROM 表名是逐行删除 DML,可回滚、不重置自增;TRUNCATE 是整表重建 DDL,不可回滚、重置自增。
(六)聚合函数与分组查询
1. 聚合函数
对一组行进行计算,返回单个结果,常用于统计分析。
| 函数 | 说明 |
|---|---|
COUNT([DISTINCT] 表达式) |
返回匹配的行数;COUNT(*)统计所有行,COUNT(字段)忽略 NULL |
SUM([DISTINCT] 表达式) |
返回数值总和,非数值类型无意义 |
AVG([DISTINCT] 表达式) |
返回数值平均值,非数值类型无意义 |
MAX([DISTINCT] 表达式) |
返回最大值 |
MIN([DISTINCT] 表达式) |
返回最小值 |
2. 分组:GROUP BY
将 WHERE 筛选后的结果,按指定字段分组,字段值相同的分为一组,配合聚合函数实现分组统计。
SELECT 分组字段, 聚合函数, ...
FROM 表名
[WHERE 条件]
GROUP BY 分组字段1, 分组字段2, ...;
核心规则(标准 SQL / MySQL 默认模式) : SELECT 后面的字段,必须是分组字段,或者聚合函数;非分组字段不能直接出现在 SELECT 中。
-- 错误:id不是分组字段,也不是聚合函数(开启ONLY_FULL_GROUP_BY时会报错)
SELECT id, COUNT(*) FROM test GROUP BY ename;
-- 正确:按ename分组,统计每组人数
SELECT ename, COUNT(*) AS num FROM test GROUP BY ename;
说明:MySQL 5.7 及以上默认开启
ONLY_FULL_GROUP_BY模式,强制遵守上述规范;关闭模式下虽允许非分组列,但返回的行数据不确定,属于不规范写法,生产环境禁止使用。
3. 分组后筛选:HAVING
对 GROUP BY 分组后的结果进行条件过滤,必须配合 GROUP BY 使用。
- WHERE:分组前筛选行,不能包含聚合函数
- HAVING:分组后筛选分组,通常包含聚合函数
示例:
-- 统计每个部门的平均工资,筛选出平均工资>8000的部门
SELECT dept, AVG(salary) AS avg_sal
FROM employee
GROUP BY dept
HAVING AVG(salary) > 8000;
(七)SQL 逻辑执行顺序总览
再次明确完整的执行顺序,这是理解所有 SQL 语法的核心:
| 执行顺序 | 子句 | 作用 |
|---|---|---|
| 1 | FROM / JOIN | 确定数据源,生成笛卡尔积,得到初始中间表 |
| 2 | WHERE | 按条件筛选行,过滤不符合的记录 |
| 3 | GROUP BY | 对筛选后的行进行分组 |
| 4 | HAVING | 对分组后的结果进行条件过滤 |
| 5 | SELECT | 投影列:选择字段、计算表达式、生成别名 |
| 6 | DISTINCT | 对结果集去重 |
| 7 | ORDER BY | 对最终结果集按指定列排序 |
| 8 | LIMIT | 截取最终返回的行数 |
三、知识拓展与补充
1. SELECT * 为什么会影响索引?B + 树会变吗?
B + 树索引本身不会因为查询语句改变。索引叶子节点只存储索引字段 + 主键,如果查询的字段都在索引中(覆盖索引),就可以直接从索引返回结果,无需回表。 使用SELECT *时,需要查询所有字段,通常必须回表访问聚簇索引,优化器评估成本后可能会放弃索引、选择全表扫描,导致性能下降。
2. INSERT 的其他实用写法
-
INSERT ... SELECT:从另一张表查询结果并插入,实现批量数据迁移INSERT INTO table2 (id, name) SELECT id, name FROM table1 WHERE id > 10; -
INSERT IGNORE:插入冲突时直接忽略,不报错也不更新INSERT IGNORE INTO test (id) VALUES (1);
3. DELETE、TRUNCATE、DROP 的核心区别
| 操作 | 类型 | 作用 | 可回滚 | 重置自增 | 速度 |
|---|---|---|---|---|---|
| DELETE | DML | 删除指定行 | 是 | 否 | 慢(逐行) |
| TRUNCATE | DDL | 清空整表,重建表结构 | 否 | 是 | 快 |
| DROP | DDL | 删除整个表(结构 + 数据) | 否 | - | 最快 |
4. GROUP BY WITH ROLLUP
在分组统计的基础上,自动生成各级汇总行,适合报表统计场景:
SELECT dept, job, COUNT(*)
FROM employee
GROUP BY dept, job WITH ROLLUP;