第二单元 · 精准修改、安全删除、基础汇总 · 第 5 / 21 节
删一行,以及删错了还能不能退回来
先 SELECT 再按主键 DELETE 掉 id = 6;讲清 autocommit 下删除不可撤销、START TRANSACTION + ROLLBACK 能退、COMMIT 之后不能退,以及断线时未提交事务作废。
约 20 分钟。这一节只删一行,但会花一半时间讲“删错了怎么办”。这里有一条硬规矩:DELETE 后面必须跟 WHERE 主键。
$ / mysql> 提示符和输出。已完成 0 / 2 步
你现在在哪个提示符
应该在 mysql>。断了就先 ssh root@8.148.204.152 → mysql-learning,然后核对起点:
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
+----+--------+------+--------+------------+
| id | name | age | city | joined |
+----+--------+------+--------+------------+
| 1 | 张伟 | 23 | 北京 | 2024-03-02 |
| 2 | 李娜 | 21 | 上海 | 2024-03-05 |
| 3 | 王强 | 25 | 广州 | 2024-02-20 |
| 4 | 赵敏 | 22 | 深圳 | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
| 6 | 周舟 | 23 | 北京 | 2024-02-28 |
+----+--------+------+--------+------------+
6 rows in set (0.00 sec)
6 行、id = 6 的周舟还在,说明第 4 节做完了,可以直接往下。如果只剩 5 行、没有周舟,说明这一节你之前已经删过,跳到「断线再学怎么核」。如果报 1146,表还没建,回第 4 节。
删之前先看清楚要删谁
这一节要删的是周舟,id = 6。用姓当成线索查一下:
SELECT id, name, city FROM lesson02_students WHERE name LIKE '周%';
+----+--------+--------+
| id | name | city |
+----+--------+--------+
| 6 | 周舟 | 北京 |
+----+--------+--------+
1 row in set (0.00 sec)
姓周的只有一个人,id = 6。但姓名可能重名,id 不会——所以真正动手用的条件还是主键:
SELECT COUNT(*) AS 人数 FROM lesson02_students;
+--------+
| 人数 |
+--------+
| 6 |
+--------+
1 row in set (0.00 sec)
记下这个 6,删完应该是 5。
为什么这一节的 DELETE 一律带 WHERE
DELETE FROM 表 WHERE 条件 会删掉满足条件的行。如果没有 WHERE,MySQL 会把“所有行”当成条件——它会照做,不会拦你,也不会问“确定吗”:
说明 为什么不写能复制的“清空整表”命令
DELETE FROM 表名; 这种写法本身合法,删的是整张表的全部行,删完表还在、行没了。它也是新手最容易误伤自己的写法:粘贴时少了一行 WHERE,整张表就空了。
所以教程不提供任何可以直接复制执行的无条件 DELETE(同样不提供无条件 UPDATE)。你要验证后果,用推理就够了:把下面那条删除语句的 WHERE id = 6 删掉,Query OK 后面的行数就会从 1 变成 6。
删掉 id = 6
DELETE FROM lesson02_students WHERE id = 6;
Query OK, 1 row affected (0.01 sec)
1 row affected 说明真的删掉了一行。再用两条查询确认删的是谁、还剩几行:
SELECT COUNT(*) AS 人数 FROM lesson02_students;
+--------+
| 人数 |
+--------+
| 5 |
+--------+
1 row in set (0.00 sec)
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
+----+--------+------+--------+------------+
| id | name | age | city | joined |
+----+--------+------+--------+------------+
| 1 | 张伟 | 23 | 北京 | 2024-03-02 |
| 2 | 李娜 | 21 | 上海 | 2024-03-05 |
| 3 | 王强 | 25 | 广州 | 2024-02-20 |
| 4 | 赵敏 | 22 | 深圳 | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
+----+--------+------+--------+------------+
5 rows in set (0.00 sec)
周舟不见了,其他 5 行的 name、age、city、joined 和第 4 节结尾一模一样。这一条就是本节之后所有页面的起点:5 行,id 1 到 5。
再跑一次同一条命令:
DELETE FROM lesson02_students WHERE id = 6;
Query OK, 0 rows affected (0.00 sec)
0 rows affected 不是出错,是已经没有 6 号了。DELETE 和 UPDATE 一样,Query OK 只说明语句跑通了;到底动了什么,看后面的行数。
删错了:什么时候能退回来
刚才那条 DELETE 是自动提交的。意思是:语句一结束,MySQL 就把它当成已经生效、已经写定,没有“上一步”可以按。
说明 “自动提交”是什么意思(一句话)
MySQL 默认 autocommit = 1:每一条单独的 INSERT、UPDATE、DELETE 执行完就立刻生效,相当于你自己按了一次“确认”。这个开关默认就是开着的,你不用去关它。
一旦生效,ROLLBACK 命令也帮不上忙——它只能撤销“还没确认”的操作。
说明 亲眼看一下:删完再 ROLLBACK,行回不来(可选做,会删掉一行再补回来)
删掉王强(id = 3),这条删除前后没有 START TRANSACTION:
DELETE FROM lesson02_students WHERE id = 3;
Query OK, 1 row affected (0.01 sec)
现在想退回:
ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
SELECT COUNT(*) AS 人数 FROM lesson02_students;
+--------+
| 人数 |
+--------+
| 4 |
+--------+
1 row in set (0.00 sec)
4 行。ROLLBACK 显示了 Query OK,可是王强没回来——因为那条删除在执行完的那一刻就已经确认了。
唯一的补救是把同一行重新插入,id 和原来的值都要对:
INSERT INTO lesson02_students (id, name, age, city, joined)
VALUES (3, '王强', 25, '广州', '2024-02-20');
Query OK, 1 row affected (0.00 sec)
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
+----+--------+------+--------+------------+
| id | name | age | city | joined |
+----+--------+------+--------+------------+
| 1 | 张伟 | 23 | 北京 | 2024-03-02 |
| 2 | 李娜 | 21 | 上海 | 2024-03-05 |
| 3 | 王强 | 25 | 广州 | 2024-02-20 |
| 4 | 赵敏 | 22 | 深圳 | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
+----+--------+------+--------+------------+
5 rows in set (0.00 sec)
回到 5 行。注意这是“重新插入”,不是“撤销”;万一原值记错了,补回来的就是错的数据。
唯一能把这道关门挡住的,是在删之前先手动开一个事务。事务就是“把接下来几条语句打成一包”,包还没确认之前,可以整包撤回。
能退回来的版本:START TRANSACTION → 改 → ROLLBACK
先开包:
START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
在包里改一行。选陈晨(id = 5)的城市,把杭州改成宁波:
UPDATE lesson02_students SET city = '宁波' WHERE id = 5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
在同一个窗口里查,能看见改后的样子:
SELECT id, name, city FROM lesson02_students WHERE id = 5;
+----+--------+--------+
| id | name | city |
+----+--------+--------+
| 5 | 陈晨 | 宁波 |
+----+--------+--------+
1 row in set (0.00 sec)
现在撤回:
ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
再查:
SELECT id, name, city FROM lesson02_students WHERE id = 5;
+----+--------+--------+
| id | name | city |
+----+--------+--------+
| 5 | 陈晨 | 杭州 |
+----+--------+--------+
1 row in set (0.00 sec)
杭州回来了。ROLLBACK = 把这一包里所有还没确认的改动全部作废,回到 START TRANSACTION 那一刻。
删除也能这样退回来。先开包:
START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
删掉赵敏(id = 4):
DELETE FROM lesson02_students WHERE id = 4;
Query OK, 1 row affected (0.00 sec)
查行数,只剩 4:
SELECT COUNT(*) AS 人数 FROM lesson02_students;
+--------+
| 人数 |
+--------+
| 4 |
+--------+
1 row in set (0.00 sec)
撤回:
ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
SELECT COUNT(*) AS 人数 FROM lesson02_students;
+--------+
| 人数 |
+--------+
| 5 |
+--------+
1 row in set (0.00 sec)
还是回到 5 行。能撤回的前提有三个,缺一个都不行:
- 修改发生在
START TRANSACTION和ROLLBACK之间,中间没有COMMIT; ROLLBACK在同一个连接里执行——也就是同一个mysql>窗口。连接断了,这一包就自动作废(撤不撤都一样);- 表用的是 InnoDB 引擎。MySQL 8.0 默认就是 InnoDB,
lesson02_students也是。
退不回来的版本:COMMIT 之后
COMMIT = 确认这一包,写定。写定之后,ROLLBACK 是一条空命令:它照常显示 Query OK,但什么都不会退。
先开包、改、确认:
START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
UPDATE lesson02_students SET city = '宁波' WHERE id = 5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
COMMIT;
Query OK, 0 rows affected (0.01 sec)
现在已经写定了。再试一次撤回:
ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
SELECT id, name, city FROM lesson02_students WHERE id = 5;
+----+--------+--------+
| id | name | city |
+----+--------+--------+
| 5 | 陈晨 | 宁波 |
+----+--------+--------+
1 row in set (0.00 sec)
还是宁波。ROLLBACK 显示 Query OK 却什么都没退,这就是“已经确认”的含义。
把城市改回杭州——这一次不是撤回,而是重新执行一条 UPDATE 改正:
UPDATE lesson02_students SET city = '杭州' WHERE id = 5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
记住这一条:
COMMIT之后没有办法撤回。想“删了还能退”,只能在删之前开事务;已经COMMIT或者本来就是自动提交的删除,只能靠重新 INSERT 同一行来补,而不是ROLLBACK。
断线也要讲清楚
如果事务还没 ROLLBACK 或 COMMIT,这个窗口就断了(网线掉了、电脑合上、exit 退出),那一包改动会自动作废。听起来像好事,但它说明另一件事:事务不是“存档”,它只在连接活着、并且你主动选择的时候才有用。
所以这一节的干净做法仍然是:DELETE ... WHERE 主键 先 SELECT 核对,确认了再执行;事务只在你打算“试着改、不满意就退”的时候用。
断线再学怎么核
重连之后(ssh → mysql-learning),一条命令看清现在的状态:
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
+----+--------+------+--------+------------+
| id | name | age | city | joined |
+----+--------+------+--------+------------+
| 1 | 张伟 | 23 | 北京 | 2024-03-02 |
| 2 | 李娜 | 21 | 上海 | 2024-03-05 |
| 3 | 王强 | 25 | 广州 | 2024-02-20 |
| 4 | 赵敏 | 22 | 深圳 | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
+----+--------+------+--------+------------+
5 rows in set (0.00 sec)
对表里的三件事:行数是 5、**没有 id = 6、陈晨的城市是杭州**。三条都对,说明删除和事务演示都收干净了,可以去第 6 节。
再试删一次 6 号是安全的,它什么都不会删:
DELETE FROM lesson02_students WHERE id = 6;
Query OK, 0 rows affected (0.00 sec)
练习
先自己做,再展开。
1. 自己加一行临时数据(随便一个不重复的 id),看一眼,再只删掉这一行。
参考答案 参考答案
INSERT INTO lesson02_students (id, name, age, city, joined)
VALUES (7, '孙悦', 30, '北京', '2024-04-02');
Query OK, 1 row affected (0.00 sec)
SELECT id, name, city FROM lesson02_students WHERE id = 7;
+----+--------+--------+
| id | name | city |
+----+--------+--------+
| 7 | 孙悦 | 北京 |
+----+--------+--------+
1 row in set (0.00 sec)
DELETE FROM lesson02_students WHERE id = 7;
Query OK, 1 row affected (0.01 sec)
删完再 SELECT COUNT(*) 应该是 5。WHERE id = 7 少不得:少了它删的就是整张表。
2. 下面三种写法,同一条删除会在什么时候真正生效?
- A:不写
START TRANSACTION,直接DELETE ... WHERE id = ...; - B:
START TRANSACTION;→DELETE ...→ROLLBACK; - C:
START TRANSACTION;→DELETE ...→COMMIT;→ROLLBACK;
参考答案 参考答案
A 立刻生效,删完就删完了,ROLLBACK 帮不上。 B 中删除在 ROLLBACK 时被作废,行还在。 C 中 COMMIT 时删除写定,最后的 ROLLBACK 什么都不做,行已经没了。
一句话:只有“还没 COMMIT 的、同一个连接里的事务”,ROLLBACK 才能退。
**3. 删错一行之后,你发现是 20 分钟前删的、当时没用事务。ROLLBACK 有用吗?**
参考答案 参考答案
没用。那条删除早就自动提交、写定了,ROLLBACK 只对“当前还没确认的操作”有效。能做的只有回到第 4 节的做法:用原来的值重新 INSERT 一行(主键 id 要一样)。所以要删数据前先 SELECT 核对,比事后补救可靠。
4. 一个“删了以后没法退”的操作里,最容易被忽略的那一步是哪个?
参考答案 参考答案
COMMIT。很多人以为开着事务就安全了,结果习惯性地 COMMIT 一下——那一刻起 ROLLBACK 就失效了。事务的价值在于“确认之前可以反悔”,确认了就不再是草稿。
做到这里
做到这里今天可以停。表里剩 5 行(id 1 到 5),你会用 WHERE 主键删一行,也知道 ROLLBACK 什么时候有用、什么时候只是个空命令。
下一节不再增删改,只做统计:COUNT、SUM、AVG、MIN、MAX,再按城市分组。数字都以这 5 行为准,所以这一节结束时状态必须收干净。
说明 这一节和视频课、讲义是什么关系(可选读)
DELETE 与事务的基础属于课程 DML 与事务部分,视频里通常在 emp 表上演示。本页的 lesson02_students、删周舟、杭州改宁波这些例子都是本教程新写的,不是视频或讲义原文,也没有复制讲义正文。
事务只讲了最小的一圈:START TRANSACTION / ROLLBACK / COMMIT,加上“自动提交”“同一连接”“InnoDB”三个前提。隔离级别、锁、保存点这些没有讲,留到需要的时候再说。
页面里的结果是本机独立的 MySQL 8.0.46 的真实输出(与学习服务器同版本号,不是同一台),包括删完再删的 0 rows affected、COMMIT 之后 ROLLBACK 无效,以及“客户端进程被杀后未提交改动消失”这一场景(验证时用直接终止客户端进程模拟了断线)。