MySQL不看视频 · SSH 实操

第二单元 · 精准修改、安全删除、基础汇总 · 第 5 / 21 节

删一行,以及删错了还能不能退回来

先 SELECT 再按主键 DELETE 掉 id = 6;讲清 autocommit 下删除不可撤销、START TRANSACTION + ROLLBACK 能退、COMMIT 之后不能退,以及断线时未提交事务作废。

2 个步骤 · 进度只存本机

约 20 分钟。这一节只删一行,但会花一半时间讲“删错了怎么办”。这里有一条硬规矩:DELETE 后面必须跟 WHERE 主键。

SSH / Linux 终端MySQL 终端输出示例(只读)复制按钮只复制命令,不含 $ / mysql> 提示符和输出。

已完成 0 / 2 步

你现在在哪个提示符

应该在 mysql>。断了就先 ssh root@8.148.204.152 → mysql-learning,然后核对起点:

MySQL 终端sql
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+--------+------------+
| 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。用姓当成线索查一下:

MySQL 终端sql
SELECT id, name, city FROM lesson02_students WHERE name LIKE '周%';
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+--------+
| id | name   | city   |
+----+--------+--------+
|  6 | 周舟   | 北京   |
+----+--------+--------+
1 row in set (0.00 sec)

姓周的只有一个人,id = 6。但姓名可能重名,id 不会——所以真正动手用的条件还是主键:

MySQL 终端sql
SELECT COUNT(*) AS 人数 FROM lesson02_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| 人数   |
+--------+
|      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

MySQL 终端sql
DELETE FROM lesson02_students WHERE id = 6;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.01 sec)

1 row affected 说明真的删掉了一行。再用两条查询确认删的是谁、还剩几行:

MySQL 终端sql
SELECT COUNT(*) AS 人数 FROM lesson02_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| 人数   |
+--------+
|      5 |
+--------+
1 row in set (0.00 sec)
MySQL 终端sql
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+--------+------------+
| 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。

再跑一次同一条命令:

MySQL 终端sql
DELETE FROM lesson02_students WHERE id = 6;
输出示例(只读,不要粘贴执行)text不可复制
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:

MySQL 终端sql
DELETE FROM lesson02_students WHERE id = 3;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.01 sec)

现在想退回:

MySQL 终端sql
ROLLBACK;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)
MySQL 终端sql
SELECT COUNT(*) AS 人数 FROM lesson02_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| 人数   |
+--------+
|      4 |
+--------+
1 row in set (0.00 sec)

4 行。ROLLBACK 显示了 Query OK,可是王强没回来——因为那条删除在执行完的那一刻就已经确认了。

唯一的补救是把同一行重新插入,id 和原来的值都要对:

MySQL 终端sql
INSERT INTO lesson02_students (id, name, age, city, joined)
VALUES (3, '王强', 25, '广州', '2024-02-20');
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.00 sec)
MySQL 终端sql
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+--------+------------+
| 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

先开包:

MySQL 终端sql
START TRANSACTION;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)

在包里改一行。选陈晨(id = 5)的城市,把杭州改成宁波:

MySQL 终端sql
UPDATE lesson02_students SET city = '宁波' WHERE id = 5;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

在同一个窗口里查,能看见改后的样子:

MySQL 终端sql
SELECT id, name, city FROM lesson02_students WHERE id = 5;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+--------+
| id | name   | city   |
+----+--------+--------+
|  5 | 陈晨   | 宁波   |
+----+--------+--------+
1 row in set (0.00 sec)

现在撤回:

MySQL 终端sql
ROLLBACK;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)

再查:

MySQL 终端sql
SELECT id, name, city FROM lesson02_students WHERE id = 5;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+--------+
| id | name   | city   |
+----+--------+--------+
|  5 | 陈晨   | 杭州   |
+----+--------+--------+
1 row in set (0.00 sec)

杭州回来了。ROLLBACK = 把这一包里所有还没确认的改动全部作废,回到 START TRANSACTION 那一刻。

删除也能这样退回来。先开包:

MySQL 终端sql
START TRANSACTION;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)

删掉赵敏(id = 4):

MySQL 终端sql
DELETE FROM lesson02_students WHERE id = 4;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.00 sec)

查行数,只剩 4:

MySQL 终端sql
SELECT COUNT(*) AS 人数 FROM lesson02_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| 人数   |
+--------+
|      4 |
+--------+
1 row in set (0.00 sec)

撤回:

MySQL 终端sql
ROLLBACK;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)
MySQL 终端sql
SELECT COUNT(*) AS 人数 FROM lesson02_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| 人数   |
+--------+
|      5 |
+--------+
1 row in set (0.00 sec)

还是回到 5 行。能撤回的前提有三个,缺一个都不行:

  1. 修改发生在 START TRANSACTION 和 ROLLBACK 之间,中间没有 COMMIT;
  2. ROLLBACK 在同一个连接里执行——也就是同一个 mysql> 窗口。连接断了,这一包就自动作废(撤不撤都一样);
  3. 表用的是 InnoDB 引擎。MySQL 8.0 默认就是 InnoDB,lesson02_students 也是。

退不回来的版本:COMMIT 之后

COMMIT = 确认这一包,写定。写定之后,ROLLBACK 是一条空命令:它照常显示 Query OK,但什么都不会退。

先开包、改、确认:

MySQL 终端sql
START TRANSACTION;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)
MySQL 终端sql
UPDATE lesson02_students SET city = '宁波' WHERE id = 5;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
MySQL 终端sql
COMMIT;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.01 sec)

现在已经写定了。再试一次撤回:

MySQL 终端sql
ROLLBACK;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)
MySQL 终端sql
SELECT id, name, city FROM lesson02_students WHERE id = 5;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+--------+
| id | name   | city   |
+----+--------+--------+
|  5 | 陈晨   | 宁波   |
+----+--------+--------+
1 row in set (0.00 sec)

还是宁波。ROLLBACK 显示 Query OK 却什么都没退,这就是“已经确认”的含义。

把城市改回杭州——这一次不是撤回,而是重新执行一条 UPDATE 改正:

MySQL 终端sql
UPDATE lesson02_students SET city = '杭州' WHERE id = 5;
输出示例(只读,不要粘贴执行)text不可复制
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),一条命令看清现在的状态:

MySQL 终端sql
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+--------+------------+
| 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 号是安全的,它什么都不会删:

MySQL 终端sql
DELETE FROM lesson02_students WHERE id = 6;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)

练习

先自己做,再展开。

1. 自己加一行临时数据(随便一个不重复的 id),看一眼,再只删掉这一行。

参考答案 参考答案
MySQL 终端sql
INSERT INTO lesson02_students (id, name, age, city, joined)
VALUES (7, '孙悦', 30, '北京', '2024-04-02');
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.00 sec)
MySQL 终端sql
SELECT id, name, city FROM lesson02_students WHERE id = 7;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+--------+
| id | name   | city   |
+----+--------+--------+
|  7 | 孙悦   | 北京   |
+----+--------+--------+
1 row in set (0.00 sec)
MySQL 终端sql
DELETE FROM lesson02_students WHERE id = 7;
输出示例(只读,不要粘贴执行)text不可复制
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 无效,以及“客户端进程被杀后未提交改动消失”这一场景(验证时用直接终止客户端进程模拟了断线)。