目录
[一、回顾 CRUD](#一、回顾 CRUD)
[1. UPDATE 基本语法](#1. UPDATE 基本语法)
[2. 修改操作案例](#2. 修改操作案例)
[2.1 修改单条记录](#2.1 修改单条记录)
[2.2 修改多条记录](#2.2 修改多条记录)
[2.3 在字段原有数值上修改](#2.3 在字段原有数值上修改)
[3. UPDATE 安全防范](#3. UPDATE 安全防范)
[三、Delete与 Truncate](#三、Delete与 Truncate)
[1. DELETE 语法与核心案例](#1. DELETE 语法与核心案例)
[1.2 删除指定单条记录](#1.2 删除指定单条记录)
[1.3 删除多条记录](#1.3 删除多条记录)
[2. TRUNCATE 语句](#2. TRUNCATE 语句)
[3. TRUNCATE 与 DELETE 的区别](#3. TRUNCATE 与 DELETE 的区别)
[4. 案例演示](#4. 案例演示)
[1. 语法结构与运行机制](#1. 语法结构与运行机制)
[2. 数据迁移案例](#2. 数据迁移案例)
[3. 数据备份案例](#3. 数据备份案例)
[4. 注意事项](#4. 注意事项)
[1. 什么是聚合函数](#1. 什么是聚合函数)
[2. 聚合函数详解](#2. 聚合函数详解)
[3. 操作示例](#3. 操作示例)
[3.1 统计总行数](#3.1 统计总行数)
[3.2 求和(SUM)与平均值(AVG)](#3.2 求和(SUM)与平均值(AVG))
[3.3 最值查询(MAX / MIN)](#3.3 最值查询(MAX / MIN))
[3.4 聚合函数组合使用](#3.4 聚合函数组合使用)
[六、GROUP BY 分组查询](#六、GROUP BY 分组查询)
[1. GROUP BY 概念与语法](#1. GROUP BY 概念与语法)
[2. GROUP BY 注意事项](#2. GROUP BY 注意事项)
[3. 聚合与分组实战案例](#3. 聚合与分组实战案例)
[4.1 统计每个班级的总人数](#4.1 统计每个班级的总人数)
[4.2 统计数学平均分与语文总分](#4.2 统计数学平均分与语文总分)
[4.3 查询最高分与最低分](#4.3 查询最高分与最低分)
[4.4 多字段组合分组](#4.4 多字段组合分组)
一、回顾 CRUD
在上一篇博客中,我们正式开启了数据操作的大门,深入演练了数据的创建(Create)与基础检索(Retrieve)。然而,现实业务中的数据绝不是静止不变,它会随着业务的发展而发生流转与演变
CRUD 的生命周期
CRUD代表了一行数据从诞生到消亡的完整生命周期。我们可以将这四个核心因子在底层数据库中的状态演变做如下精简回顾:
-
C (Create / 写入): 数据从无到有,通过 INSERT 持久化到磁盘上
-
R (Retrieve / 读取): 数据的展现,通过 SELECT 配合各种条件将数据筛选出来
-
U (Update / 修改): 数据的更新。当学生的考试成绩录入错误、或者用户的账户余额发生变动时,我们需要在原有的基础上,对指定字段的数值进行修正
-
D (Delete / 销毁): 数据的销毁。当数据失效、用户注销或测试数据需要清理时,我们需要将其从存储页中彻底移除
二、Update:修改数据
在数据库的日常维护中,数据变更极其高频。例如学生申请成绩复核发现分数录入错误、或者用户修改了个人资料。在 MySQL 中,这类对现有行记录进行修正的操作由 UPDATE 语句执行
1. UPDATE 基本语法
UPDATE 语句的核心逻辑是:锁定符合条件的行,然后将指定的列修改为新的值。
语法结构
sql
UPDATE table_name
SET column1 = value1, column2 = value2, ...
[WHERE condition];
-
SET 子句: 后面紧跟需要修改的字段名和新值,多个字段之间用逗号
,分隔。新值可以是明确的常量,也可以是动态的计算表达式 -
WHERE 子句: 用于过滤出需要修改的行。如果省略,整个数据表中的所有记录都将被强制修改
2. 修改操作案例
我们继续沿用之前的 student_score 成绩表数据,来演示不同业务场景下的修改行为
2.1 修改单条记录
业务需求: 学号为 '20260001' 的同学(唐三藏)经成绩复核,其语文和英语成绩录入有误。需将语文成绩修正为 75.00,英语成绩修正为 60.00
sql
UPDATE student_score
SET chinese_score = 75.00, english_score = 60.00
WHERE student_no = '20260001';


2.2 修改多条记录
业务需求: 因期末考试数学难度偏大,学校决定将所有数学成绩低于 60.00 分的同学,其数学分数统一修改为 60.00 分
sql
UPDATE student_score
SET math_score = 60.00
WHERE math_score < 60.00;
执行该语句后,原本数学为 42.50 分的"猪八戒"将被成功修改为 60.00 分

小龙女的数学成绩原本为 NULL。由于 NULL < 60.00 的逻辑判定结果为 UNKNOWN,因此小龙女的成绩不会被这条语句修改。如果希望连同缺考的一并修改,WHERE 条件必须写成 WHERE math_score < 60.00 OR math_score IS NULL
2.3 在字段原有数值上修改
在实际项目开发中,我们常常需要基于字段的当前值进行累加或累减(例如电商系统里的商品库存扣减、或者本例中的全员加分)
业务需求: 为了奖励同学们在英语竞赛中的优秀表现,决定为全校所有拥有有效英语成绩的同学,在原分数基础上统一加上 5.00 分
sql
UPDATE student_score
SET english_score = english_score + 5.00
WHERE english_score IS NOT NULL;

此时 SET 子句右侧的 english_score + 5.00 会先读取每一行的原值,执行加法计算后,再将新结果覆盖写入该行
3. UPDATE 安全防范
由于 UPDATE 会造成极大的影响,在真实的生产项目和团队协作中,必须严格遵守以下原则:
如果你在执行修改时,不小心写错或漏写了 WHERE 子句:
sql
-- 没有 WHERE 条件
UPDATE student_score SET name = '大魔王';
这条语句一旦在中大型系统的生产环境中执行,整张表几十万用户的姓名将在瞬间全部变成 "大魔王",属于极其严重的生产事故
操作规范
-
**安全更新模式:**在客户端或全局配置中开启该参数。一旦开启,如果 UPDATE 语句中没有包含 WHERE 子句,或者 WHERE 子句中没有利用到主键或索引列,MySQL 引擎将直接拒绝执行并抛出错误
sqlSET sql_safe_updates = 1; -- 开启安全锁 -
修改前先执行 SELECT 验证: 在对重要数据执行 UPDATE 之前,强烈建议先将 UPDATE ... SET ... 替换为 SELECT *,并配合相同的 WHERE 条件进行查询。确认检索出来的行完全符合修改预期后,再放行修改语句
-
高危操作前必须备份: 涉及大批量数据修剪时,先通过快照、历史表或事务机制对原始数据进行锁定备份
三、Delete与 Truncate
在数据的生命周期中,有数据的录入,自然也有数据的销毁。在 MySQL 中,如果某些历史测试数据不再需要,或者用户申请注销了账号,我们需要彻底移除这些记录。销毁数据主要有两种方法:DELETE 语句与 TRUNCATE 语句
1. DELETE 语法与核心案例
DELETE 属于标准的数据操作语言(DML),它的核心职责是逐行删除符合条件的记录
语法结构
sql
DELETE FROM table_name [WHERE condition];
WHERE 子句(再度高警): 用于限定删除的范围。如果不带 WHERE 子句,则代表删除整张表中的所有行数据(但会保留表结构、索引和约束)
1.2 删除指定单条记录
**业务需求:**接学校通知,学号为 '20260012' 的同学(镇元大仙)已办理转学手续,需将其从成绩表中彻底移除
sql
DELETE FROM student_score
WHERE student_no = '20260012';

1.3 删除多条记录
业务需求: 清理当前表内语文成绩低于 60 分的同学
sql
DELETE FROM student_score
WHERE chinese_score < 60.00;
原本语文成绩为 55.00 分的 "猪八戒" 和 "牛魔王" 所在的物理行将被彻底抹去

2. TRUNCATE 语句
当面对千万级甚至上亿级别的历史日志表或临时表,如果需要彻底清空整张表的数据,使用 DELETE FROM table 会引发严重的系统灾难(导致海量的事务日志写入、锁表以及极长的等待时间)。为了应对这种 "全表清除" 的高频极端场景,MySQL 提供了 TRUNCATE 命令
语法结构
sql
TRUNCATE TABLE table_name;
TRUNCATE 属于数据定义语言(DDL) 。它不具备按条件筛选的能力(不能加 WHERE 子句),一旦执行,整张表将被瞬间秒级清空
3. TRUNCATE 与 DELETE 的区别
它们在底层的运行机制、性能消耗以及对系统计数器的影响上有着巨大差异
| DELETE | TRUNCATE | |
|---|---|---|
| 语言分类 | DML(数据操作语言) | DDL(数据定义语言) |
| 条件筛选 | 支持,可以做到精准删除某几行 | 绝对不支持,只能全表数据斩草除根 |
| 底层实现机制 | 逐行删除:扫描每一行,将其标记为删除 | 直接将原表物理文件截断(释放数据页),重建一张全新空表 |
| 执行速度与效率 | 数据量大时极慢(伴随频繁的磁盘 I/O) | 极快,无论表里有百万还是千万条数据 |
| 事务回滚 | 记录完整的 Undo Log,支持事务回滚 | 不记录行级日志,无法回滚(隐式提交) |
| 自增计数器影响 | 不重置自增值,后续插入将在原最大值上继续 | 重置自增长计数器,恢复从初始值 1 开始 |
4. 案例演示
为了看清两者的核心差异,我们通过一个对照实验,观察在清空表数据后,自增长主键 id 的行为特征
实验一:使用 DELETE 全表删除
-
清空整张表:
sqlDELETE FROM student_score; -
此时表内数据全部清空。我们再插入一条全新的测试记录:
sqlINSERT INTO student_score (student_no, name) VALUES ('20261001', '新同学A'); -
查看该同学的 id 状态:
SELECT id, name FROM student_score;
输出结果: id 值为 16。 结论:DELETE 不会改变内存中的自增长计数器,即使清空了全表,它依然记着曾经达到的最大值
实验二:使用 TRUNCATE 截断表
-
接着在刚才的状态下,我们执行截断表:
TRUNCATE TABLE student_score; -
同样再次插入一条完全一样的全新测试记录:
sqlINSERT INTO student_score (student_no, name) VALUES ('20261002', '新同学B'); -
再次查看该同学的 id 状态:
SELECT id, name FROM student_score;
输出结果: id 值为 1。 结论:TRUNCATE 将原表结构直接推倒重来,自增长计数器被彻底清零重置
四、插入查询结果
在实际的项目开发和数据库运维中,我们经常会遇到数据迁移、历史数据归档的场景。如果先用 SELECT 将数据读取到后端程序中,再批量 INSERT 回数据库,不仅会白白消耗巨大的网络带宽,还会引发应用层内存溢出的风险
为了实现高效的数据搬运,MySQL 提供了 INSERT INTO ... SELECT语法
1. 语法结构与运行机制
该语法允许你将一条复杂的 SELECT 查询结果集,直接当作数据源灌入到另一张目标表中
语法结构
sql
INSERT INTO target_table [(target_column_list)]
SELECT source_column_list
FROM source_table
[WHERE condition];
底层运行机制
MySQL 引擎在底层会开启一个内部管道:由查询器筛选出源表的数据页,直接在内存中传递给写入器写入目标表,整个过程完全在数据库内部闭环完成,速度达到秒级
核心要求: 查询结果集的列数、顺序以及数据类型,必须与目标表 (target_column_list) 声明的列高度匹配

2. 数据迁移案例
为了演示该语法,我们来演练两个最典型的真实业务场景:优秀学生迁移 与历史数据快照备份
业务需求: 学校要成立一个精英班。要求从现有的 student_score 表中,筛选出总分大于 230 分的同学,将他们的学号、姓名和总分自动迁移到一张全新的精英表(elite_student)中
步骤一:创建目标表结构
sql
CREATE TABLE elite_student (
id INT AUTO_INCREMENT PRIMARY KEY,
student_no VARCHAR(10) NOT NULL UNIQUE,
name VARCHAR(50) NOT NULL,
total_score DECIMAL(6, 2) COMMENT '总分'
) COMMENT '精英学生表';
步骤二:执行 INSERT SELECT 进行迁移
sql
INSERT INTO elite_student (student_no, name, total_score)
SELECT
student_no,
name,
(chinese_score + math_score + english_score) AS total_score
FROM student_score
WHERE (chinese_score + math_score + english_score) > 230.00;
行为分析: SELECT 子句中的列顺次为学号、姓名、总分计算结果,正好对应 INSERT INTO 后面括号里的三个字段。执行后,系统会精准把符合条件的数据灌入新表

3. 数据备份案例
业务需求: 在对 student_score 表进行高危的大批量修改操作前,需要对其当前状态进行完整备份
步骤一:快速复制表结构
sql
-- 该命令可以完美克隆出结构、索引完全一致的空表
CREATE TABLE student_score_backup LIKE student_score;
步骤二:全表数据同步
由于两张表结构完全一模一样,我们甚至不需要写出具体的列名列表,直接进行全列灌入:
sql
INSERT INTO student_score_backup
SELECT * FROM student_score;

4. 注意事项
虽然这个语法数据迁移很方便,但在高并发的生产环境中,稍有不慎就会引发线上故障
-
**主键与唯一键冲突:**如果目标表中已经存在部分数据,执行 INSERT SELECT 时极易触发 Duplicate entry 报错导致整个迁移中断。如果不希望中断,可以配合上一章学到的冲突处理语法:
sqlINSERT INTO target_table SELECT ... ON DUPLICATE KEY UPDATE ...; -
锁表风险: 在默认的隔离级别下,MySQL 执行 INSERT ... SELECT 时,为了保证数据一致性,会对源表扫描到的行甚至间隙加上共享锁。如果你的源表是高频写入的业务表,且迁移的数据量极大,将会导致线上的正常插入/修改操作因拿不到锁而大规模阻塞,进而引发系统雪崩
最佳实践: 避免在业务高峰期执行大批量的 INSERT SELECT 操作。如果数据量过百万,务必在应用层写循环,利用 WHERE id BETWEEN ... 机制进行分批次(如每次 5000 条)的小步迁移
五、聚合函数
在前面的查询中,我们的目光始终聚焦在 "行" 的维度上------要么是筛选特定行,要么是修改或删除特定行。然而在现实的商业分析中,我们往往需要跳出单行数据的局限,从宏观的 "列" 维度去洞察整体数据的统计特征
例如:全校学生的平均分是多少?、本次考试最高分和最低分差多少?。为了应对这类垂直方向的数据统计需求,MySQL 提供了高效的聚合函数
1. 什么是聚合函数
聚合函数 是一类特殊的内置函数,它的输入是一组数据(即某一列中的多行数值) ,经过内部的数学运算后,最终压缩并返回一个单一的统计值
聚合函数对 NULL 值的处理
在关系型数据库的标准规范中,除了 COUNT(*) 之外,其余所有聚合函数(如 SUM, AVG, MAX, MIN)在进行计算时,都会自动忽略 NULL 值
2. 聚合函数详解
MySQL 中最常用、最基础的聚合函数主要有五个:
| 函数名 | 功能含义 | 支持的数据类型 |
|---|---|---|
| COUNT() | 统计符合条件的行数或非 NULL 值的个数 | 任何数据类型 |
| SUM() | 计算指定列的数值总和 | 仅数值类型(整数、浮点数) |
| AVG() | 计算指定列的数值平均值 | 仅数值类型 |
| MAX() | 找出指定列中的最大值 | 数值、字符串(按字母表)、日期 |
| MIN() | 找出指定列中的最小值 | 数值、字符串(按字母表)、日期 |
3. 操作示例
我们使用现有的 student_score 测试表(包含此前插入的缺考同学 "小龙女",其数学和英语成绩为 NULL)来进行实战演练
3.1 统计总行数
业务需求: 统计当前成绩表里一共有多少条记录
sql
-- 写法一:COUNT(*)
SELECT COUNT(*) FROM student_score;
-- 写法二:COUNT(1)
SELECT COUNT(1) FROM student_score;
-- 写法三:COUNT(column)
SELECT COUNT(math_score) FROM student_score;
COUNT(*)、COUNT(1) 与 COUNT(列名) 的区别
-
COUNT(*) 与 COUNT(1): 这两种写法在 InnoDB 存储引擎下的执行效率几乎完全相同 ,系统都会去统计最终结果集的总行数,包含 那些整行所有字段都为 NULL 的记录

-
COUNT(math_score) : 这种写法具有明确的指向性。它只统计 math_score 这一列中非 NULL 的记录个数。因为 "小龙女" 的数学成绩为 NULL,所以前两个语句返回的计数是 9,而 COUNT(math_score) 返回的计数是 8

3.2 求和(SUM)与平均值(AVG)
业务需求: 统计全校学生的语文总分,以及全校学生的平均数学成绩。
sql
SELECT SUM(chinese_score) AS total_chinese, AVG(math_score) AS avg_math
FROM student_score;

**AVG 与 NULL:**当执行 AVG(math_score) 时,由于小龙女数学为 NULL,MySQL 在计算平均分时的底层公式是:数学总分 / 8(即仅除以有成绩的 8 个人)。 若业务要求缺考按 0 分计入平均分,则需要使用 IFNULL 函数进行预处理:AVG(IFNULL(math_score, 0))
3.3 最值查询(MAX / MIN)
业务需求: 找出本次期末考试中,语文成绩的最高分是多少?英语成绩的最低分是多少?
sql
SELECT MAX(chinese_score) AS max_chinese, MIN(english_score) AS min_english
FROM student_score;

执行结果分析: MAX 将捕捉到语文分最高的同学;而 MIN 在捕捉最低分时,会自动跳过 NULL 值,从而定位到有效分数的最低点
3.4 聚合函数组合使用
在实际报表开发中,这五个函数通常会组合使用,一行 SQL 即可生成整张表的运营统计快照
sql
SELECT
COUNT(*) AS 总人数,
SUM(math_score) AS 数学总分,
AVG(math_score) AS 数学平均分,
MAX(math_score) AS 数学最高分,
MIN(math_score) AS 数学最低分
FROM student_score;
六、GROUP BY 分组查询
在真实的业务分析中,单纯统计全校平均分往往不够精细,更多时候需要看到类似:每个班级的人数是多少?、每个学科的最高分是多少?这类具有归类性质的数据
为了满足这样的需求,MySQL 提供了 GROUP BY 子句。由于分组与聚合天生共生,本章将把分组查询与聚合函数结合起来,进行深度拆解
为现有成绩表追加属性
为了完美演示大纲中要求的 "每个班级" 的分组统计案例,现有的 student_score 表中还没有班级字段。在切入正题前,我们先为成绩表追加班级维度并灌入对比数据
sql
-- 1. 为学生成绩表追加班级字段,默认所有人属于"天字一班"
ALTER TABLE student_score ADD class_name VARCHAR(20) DEFAULT '天字一班' COMMENT '班级名称';
-- 2. 将偶数行 id 的同学划分到"地字二班",制造完美的数据对比
UPDATE student_score SET class_name = '地字二班' WHERE id % 2 = 0;
1. GROUP BY 概念与语法
GROUP BY 子句的作用是:根据指定的一个或多个列的值,将整张数据表划分为若干个 "数据小桶(Buckets)"。列值相同的行会被强制归类到同一个桶中
随后,所有的聚合函数都将不再作用于整张大表,而是分别在每个 "小桶" 内部独立进行计算
语法结构
sql
SELECT column_name, AGG_RATE_FUNCTION(column_name)
FROM table_name
[WHERE condition]
GROUP BY column_name;
2. GROUP BY 注意事项
GROUP BY 的唯一原则
在显式启用了 GROUP BY 的查询中,SELECT 子句后面能够出现的字段,有且仅能有两种形态:
必须是 GROUP BY 后面跟着的那个分组字段本身
必须是被包裹在聚合函数(如 COUNT, SUM, AVG 等)内部的字段。
错误示范
sql
SELECT class_name, name, AVG(math_score)
FROM student_score
GROUP BY class_name;

-
底层原理: MySQL 在执行完 GROUP BY class_name 后,数据被分成了 "天字一班" 和 "地字二班" 两个大组。每个大组最终只能输出一行结果。然而,在一个班级组里,存在着 "唐三藏"、"猪八戒" 等多个不同的学生姓名,MySQL 此时根本不知道这一行该显示哪一个人的名字
-
系统响应: 在 MySQL 5.7 及以上版本中,由于默认开启了 ONLY_FULL_GROUP_BY 严格 SQL 模式,此类语句会直接引发 Error Code: 1055 报错,从底层直接拒绝执行,以确保数据的逻辑严密性
3. 聚合与分组实战案例
现在,我们通过三组高频的企业级报表需求,演练多维度的分组聚合
4.1 统计每个班级的总人数
业务需求: 迅速查出 "天字一班" 和 "地字二班" 当前各有学生多少人
sql
SELECT class_name AS 班级, COUNT(*) AS 班级人数
FROM student_score
GROUP BY class_name;

4.2 统计数学平均分与语文总分
业务需求: 评估不同班级的学科质量,横向对比班级的数学平均分和语文总成绩
sql
SELECT
class_name AS 班级,
AVG(math_score) AS 数学平均分,
SUM(chinese_score) AS 语文总分
FROM student_score
GROUP BY class_name;
此时,AVG 和 SUM 别在两班内部独立运算。"地字二班" 中包含缺考的 "小龙女",其 NULL 值在计算该班数学平均分时会被自动剔除,不会拖累班级平均分

4.3 查询最高分与最低分
业务需求: 摸底两极分化情况,找出各班英语的分数最高与最低
sql
SELECT
class_name AS 班级,
MAX(english_score) AS 英语最高分,
MIN(english_score) AS 英语最低分
FROM student_score
GROUP BY class_name;

4.4 多字段组合分组
GROUP BY 不仅支持单列分组,还支持多列组合。其逻辑是:只有当指定的两个字段的值都完全相同时,才会被归为一组
业务需求: 先按班级进行大类划分,在班级内部再按 "是否拥有合格英语成绩" 进行二次细分
sql
SELECT
class_name,
(english_score IS NOT NULL) AS 英语有成绩,
COUNT(*) AS 人数
FROM student_score
GROUP BY class_name, (english_score IS NOT NULL);

数据将首先切分为一班、二班,接着在一班内部划分为 "有成绩组" 和 "缺考组",实现更加细腻的多维交叉矩阵分析
总结
综上所述,我们补全了 MySQL 单表 CRUD 操作中的另外两个重要环节------数据修改与数据删除,并学习了截断表、插入查询结果等常见操作。至此,一个完整的数据增删改查流程已经基本建立起来
与此同时,我们还接触了聚合函数与分组查询,学会了对大量数据进行统计、汇总与分析。例如统计记录数量、计算平均值、查找最大值和最小值,以及按照不同条件对数据进行分组统计等
不过,在实际开发过程中,我们经常会发现:
姓名需要转换大小写;字符串需要截取;日期需要格式化;查询结果需要进行数学计算;空值需要特殊处理;
如果所有逻辑都放到程序代码中完成,不仅效率较低,也会增加开发复杂度
因此,MySQL 为我们提供了大量功能丰富的内置函数,帮助我们直接在 SQL 层面对数据进行处理与计算
下一篇中,我们将正式学习 MySQL 常见内置函数,包括字符串函数、数学函数、日期时间函数以及流程控制函数等内容,进一步提升 SQL 的数据处理能力
