MySQL不看视频 · SSH 实操

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

先查一遍,再改一行

建第二单元自己的表 lesson02_students(6 行),用同一段 WHERE 先 SELECT 再 UPDATE,读懂 Rows matched 与 Changed 的区别,并处理列名写错(1054)。

7 个步骤 · 进度只存本机

约 18 分钟。这一节建一张属于第二单元的表,然后用 UPDATE 只改一行。重点是先把条件写对、先看清楚会碰到谁,再敲回车。

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

已完成 0 / 7 步

你现在在哪个提示符

应该在 mysql>。如果 SSH 断了,先 ssh root@8.148.204.152,再 mysql-learning,最后确认自己在 itcast:

MySQL 终端sql
USE itcast;
输出示例(只读,不要粘贴执行)text不可复制
Database changed

USE 只切换当前连接用哪个库,不动数据。如果 SELECT DATABASE(); 已经显示 itcast,这句可以跳过。

为什么新建一张表,而不是改第一单元那张

第一单元的 lesson01_students 是你自己建的,行数和年龄都可能被你改过。本节要改数据,如果直接在它上面练,“应该看到什么”就对不上任何固定答案。

所以第二单元用一张新表 lesson02_students:字段和第一单元一样,行换成另一组 6 个人。第一单元那张表这一单元一个字节都不碰,想回去看原样随时可以。

先确认这张新表还没建:

MySQL 终端sql
SHOW TABLES LIKE 'lesson02_students';
输出示例(只读,不要粘贴执行)text不可复制
Empty set (0.00 sec)

Empty set 说明没有同名表,可以建。如果这里显示了一行,说明你之前建过,跳到本节的「重复学习怎么办」。

顺便看一眼第一单元那张表在不在(只读,不改它):

MySQL 终端sql
SELECT COUNT(*) AS 人数 FROM lesson01_students;
输出示例(只读,不要粘贴执行)text不可复制
ERROR 1146 (42S02): Table 'itcast.lesson01_students' doesn't exist

这条报错不是要你修。它只说明一件事:本节之后的所有步骤都不依赖第一单元那张表,没做过第一单元也能从这一节开始。

建表和 6 行数据

MySQL 终端sql
CREATE TABLE lesson02_students (
  id INT PRIMARY KEY COMMENT '学号',
  name VARCHAR(20) NOT NULL COMMENT '姓名',
  age TINYINT UNSIGNED COMMENT '年龄',
  city VARCHAR(20) COMMENT '所在城市',
  joined DATE COMMENT '加入日期'
) COMMENT='本教程第二单元练习表,与视频示例无关';
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.03 sec)

字段名和第一单元完全一样,DESC 的结果也应该一样:

MySQL 终端sql
DESC lesson02_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+------------------+------+-----+---------+-------+
| Field  | Type             | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id     | int              | NO   | PRI | NULL    |       |
| name   | varchar(20)      | NO   |     | NULL    |       |
| age    | tinyint unsigned | YES  |     | NULL    |       |
| city   | varchar(20)      | YES  |     | NULL    |       |
| joined | date             | YES  |     | NULL    |       |
+--------+------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

然后放进 6 行:

MySQL 终端sql
INSERT INTO lesson02_students (id, name, age, city, joined) VALUES
  (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, '周舟', 26, '北京', '2024-02-28');
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 6 rows affected (0.01 sec)
Records: 6  Duplicates: 0  Warnings: 0
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 | 周舟   |   26 | 北京   | 2024-02-28 |
+----+--------+------+--------+------------+
6 rows in set (0.00 sec)

这 6 行里有两处是特意安排的:id = 1 和 id = 6 都在北京,而上海只有李娜一个人。后面讲“到底改了几行”,这两处就是活教材。

改一个字之前,先把 WHERE 抄给 SELECT

UPDATE 的写法是 UPDATE 表 SET 列 = 新值 WHERE 条件。条件写错,改的就不是一行。所以这一单元的习惯是:**同一段 WHERE,先让 SELECT 跑一遍**。

MySQL 终端sql
SELECT id, name, age FROM lesson02_students WHERE city = '上海';
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  2 | 李娜   |   21 |
+----+--------+------+
1 row in set (0.00 sec)

只有李娜一行。这就说明,如果把 SELECT id, name, age 换成 UPDATE ... SET age = 24,被改的只会是她。

不写 WHERE 会怎样:UPDATE 会把它当成“这张表的每一行”。而就算写了 WHERE,条件太宽也会一次改掉好几个人。下面这条把“上海人”当成条件,如果表里有三个上海人,三个人都会被改成 24:

注意 为什么这里只给警告、不给能直接复制的命令

“忘记写 WHERE”和“WHERE 太宽”是这个操作最危险的两种写法,一条能把整张表的某一列全改掉,改完还没有回收站。教程不提供可以直接复制执行的这类语句——粘贴时少看一眼,代价就是整列数据。

自己要看后果,可以这样推:把 WHERE city = '上海' 换成 WHERE city = '北京',命令结构和上面一样,Rows matched 就会是 2。先跑 SELECT ... WHERE city = '北京',你能看到会命中的就是张伟和周舟两行。

第一次精准改一行

目标是让 id = 1 的张伟变成 30 岁。先 SELECT:

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

再改:

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

UPDATE 在 mysql> 里会回三行字,每一行在回答不同的问题:

  • Query OK, 1 row affected:语句执行成功,影响了 1 行。
  • Rows matched: 1:WHERE 条件选中了几行。
  • Changed: 1:其中值真正变了、确实写进去的有几行。

再查一次:

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

30 进去了。前后两条 SELECT 的列一样,所以两张小表可以直接对照着看。

matched 和 changed 不是一回事

先看“一行都没选中”:

MySQL 终端sql
UPDATE lesson02_students SET age = 31 WHERE id = 99;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

表里没有 id = 99,所以什么都没发生。**Query OK 不等于改成功了**。看到 matched: 0,先回头核对 id,而不是继续往下。

再看“选中了,但值本来就一样”:

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

李娜本来就在上海,把她的城市设成“上海”等于没改。matched: 1 说明人找到了,Changed: 0 说明没有必要写入。这就是它和上一条 matched: 0 的区别:一个是没找到,一个是找到了但没变化。

这也解释了一个常见疑惑:同一条 UPDATE 跑第二次,经常看到 Changed: 0。不是坏了,是值已经对了。

Changed 还可能比 matched 小:WHERE 选中了几行,其中一部分本来就已经等于新值,那几行不算改动。下一节开头的 WHERE city = '北京' 就会遇到这种情况——命中的行和真正改动的行不是一回事。

写错列名会怎样

MySQL 终端sql
UPDATE lesson02_students SET age = 24 WHERE citye = '北京';
输出示例(只读,不要粘贴执行)text不可复制
ERROR 1054 (42S22): Unknown column 'citye' in 'where clause'

citye 是故意打错的列名。1054 表示找不到这个列,整条语句没有执行,一行没动。遇到它别靠记忆猜,直接看结构:

MySQL 终端sql
DESC lesson02_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+------------------+------+-----+---------+-------+
| Field  | Type             | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id     | int              | NO   | PRI | NULL    |       |
| name   | varchar(20)      | NO   |     | NULL    |       |
| age    | tinyint unsigned | YES  |     | NULL    |       |
| city   | varchar(20)      | YES  |     | NULL    |       |
| joined | date             | YES  |     | NULL    |       |
+--------+------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

是 city,少了一个 e。列名以 DESC 为准,不以记忆为准。

一次改两行:先看清,再动手

现在把北京两个人的年龄都改成 23。先看会命中谁:

MySQL 终端sql
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+--------+------------+
| id | name   | age  | city   | joined     |
+----+--------+------+--------+------------+
|  1 | 张伟   |   30 | 北京   | 2024-03-02 |
|  2 | 李娜   |   21 | 上海   | 2024-03-05 |
|  3 | 王强   |   25 | 广州   | 2024-02-20 |
|  4 | 赵敏   |   22 | 深圳   | 2024-03-09 |
|  5 | 陈晨   |   24 | 杭州   | 2024-03-12 |
|  6 | 周舟   |   26 | 北京   | 2024-02-28 |
+----+--------+------+--------+------------+
6 rows in set (0.00 sec)

北京的年龄是 30 和 26。把这一列设成 23,两行都需要改:

MySQL 终端sql
UPDATE lesson02_students SET age = 23 WHERE city = '北京';
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 2 rows affected (0.01 sec)
Rows matched: 2  Changed: 2  Warnings: 0
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)

两张表对照:age 从 30 和 26 变成 23 和 23,name、city、joined 一个字没动。这就是“改了一列、命中两行”应该长成的样子。如果这里某个人的 joined 也变了,说明 SET 后面写错了列。

本节结束时,表必须是这样:6 行,id = 1 和 id = 6 的 age 都是 23,其余 4 行分别是 21、25、22、24。下一节从这张表删掉 id = 6 那一行。

重复学习怎么办

说明 断了、重进、想重做:三种情况分开处理

一、只是断了,想接着学。 重新 ssh → mysql-learning,然后:

MySQL 终端sql
SELECT id, name, age FROM lesson02_students ORDER BY id;

只看两处:行数是 6 吗?id = 1 和 id = 6 的 age 都是 23 吗?都对,本节就做完了,直接去第 5 节。如果 id = 1 还是 30,说明最后那条 WHERE city = '北京' 没跑,补上即可。

二、想知道本节开始时表长什么样。 不用重建,DESC 加本节第一张全表结果就是那个画面。

三、真想从本节开头再练一遍(可选)。 先数行数,别急着删:

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

如果这里的数字是 5,说明你已经做过第 5 节(删掉了 id = 6),那就别再动这张表,直接去第 5 节看注明的接续方式。如果是 7 或更多,先用 SELECT * 看清楚多了什么,再单独删掉多的那一行。

要把年龄恢复成本节开头的 23、21、25、22、24、26,不需要删表:按 id 一行一行改回去就行。不要 DROP 整张表,不要 TRUNCATE,也别去“重置 itcast 库”——第二单元的表不归这一节管,第一单元的表更不归。

练习

先自己想,再展开对答案。

**1. 把 id = 3 的加入日期改成 2024-03-01,看一眼,再改回 2024-02-20。**

参考答案 参考答案
MySQL 终端sql
UPDATE lesson02_students SET joined = '2024-03-01' WHERE id = 3;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
MySQL 终端sql
SELECT name, joined FROM lesson02_students WHERE id = 3;
输出示例(只读,不要粘贴执行)text不可复制
+--------+------------+
| name   | joined     |
+--------+------------+
| 王强   | 2024-03-01 |
+--------+------------+
1 row in set (0.00 sec)
MySQL 终端sql
UPDATE lesson02_students SET joined = '2024-02-20' WHERE id = 3;
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

日期也要加英文单引号,格式 YYYY-MM-DD。

**2. 下面两句都会显示 Query OK。各选中几行、改了几行?先猜再看。**

MySQL 终端sql
UPDATE lesson02_students SET age = 30 WHERE id = 99;
MySQL 终端sql
UPDATE lesson02_students SET city = '上海' WHERE id = 2;
参考答案 参考答案

第一句 matched: 0, changed: 0:没找到 99 号,表没变。第二句 matched: 1, changed: 0:找到了李娜,但她已经是上海,不用改。

两句都没改到数据,却都是 Query OK。所以判断改没改,要看 Rows matched 和 Changed。

**3. 现在表有 6 行,年龄最高的是 30。再用 id = 99 去改一次年龄,会发生什么?**

MySQL 终端sql
UPDATE lesson02_students SET age = 30 WHERE id = 99;
参考答案 参考答案
输出示例(只读,不要粘贴执行)text不可复制
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

什么都没有发生。Query OK 只表示语句跑通了,没有 99 号,所以既没选中也没改。想看年龄最高的人是谁、是不是 30,用汇总函数:

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

MAX 返回这一列的最大值;它是汇总函数,下一节会系统讲。

**4. 为什么这一节坚持先 SELECT 再 UPDATE?它挡住的是哪一类错误?**

参考答案 参考答案

挡住“WHERE 命中的行比你以为的多”这一类。SELECT 用的是和 UPDATE 一模一样的 WHERE,先跑一遍就能看到几行、是谁;如果多出一行,损失止于一条普通的查询。

做到这里

做到这里今天可以停。你有一张第二单元自己的表,会用 WHERE 精准改一行,也认识了 matched 和 changed 的区别。

下一节在这张表上删一行:先 SELECT 看清要删的是谁,再 DELETE,然后讲清楚什么情况下删错了能退回来,什么情况下一按回车就退不回了。

说明 这一节和视频课、讲义是什么关系(可选读)

UPDATE 属于课程 DML 部分,视频里通常在 emp 表上演示。本页的 lesson02_students、6 行数据、“把北京两个人改成 23 岁”,都是本教程为动手路径新写的,不是视频或讲义里的例子,也没有复制讲义正文。

页面里的结果都在本机独立的 MySQL 8.0.46 上实跑过(与学习服务器同版本号),包括 1054 报错、matched: 0 和 changed: 0 两种情况。0.00 sec 是测试机耗时,你屏幕上可能是 0.01 sec,行内容一致。