🦏 第7章 数据修改
📄 一句话概括

update 表 set 列 = 值 where 条件——set 说改什么,where 说改哪些行。 最好用的一招是「用原值算新值」:SET stock = stock + 10, 每行各自加 10,200 变 210、0 变 10。 但并发时它会丢更新—— 两个人各加 10 可能只加到 210。 忘写 where 就是全表(Demo 里影响 3 行)。 两条保命习惯:先用 select 验 where 的范围,再用 where 1 = 0 空跑。

📝 本章和前面几章的关系

上一篇是「加」,这一篇是「改」——三类语句里最容易出事的一种。 insert 错了顶多多几行脏数据,update 错了会把原来的值覆盖掉。 而覆盖是看不出痕迹的 :你不知道它原本是什么。

一、基本写法:set 与 where 各管一半

set 说改哪一列、改成什么;where 说改哪些行。 两个都写清楚,改动才可控。 多列用逗号连在同一个 set 后面:SET room = 'A101', teacher = '马老师'。 拆成两条 update 也能改,但要扫两遍表,还可能中途失败只改了一半。 set 里不能用聚合函数,也不能引用别的表(除非用子查询)。 要「按另一张表的值来改」,得写子查询或 join。

二、用原值算新值:最好用的一招

SET stock = stock + 10 里的 stock 指的是「这一行原来的值」。 所以每行各自加 10—— 跳绳 200 变 210、运动水壶 0 变 10。 这一招做库存扣减、计数累加、分数调整时天天用。 关键是「不要先查出来再算好写回去」, 那样多一次往返,还会碰上并发问题。 但并发时它会丢更新:两个请求同时读到 200,各自加 10 写回 210, 结果只加了一次。 这叫「丢失更新」。 解法是加锁或者用事务(第 18 章), 或者干脆依赖数据库自己的行锁——这也是为什么「用原值算」比「查出来算」安全。

三、忘写 where:不是错误,是范围错了

UPDATE classes SET teacher = '全部' 不写 where,影响 3 行,全改了。 它不报错,因为语法完全正确。 这是最阴的一类事故:语句成功、影响行数还挺「合理」, 数据却已经被覆盖,而且原来的值再也查不回来。 唯一的保险是习惯:update 打完,先回头看一眼有没有 where, 再看一眼条件对不对。 两个动作各两秒钟,能挡掉大半事故。

四、两条保命习惯:先查、再空跑

习惯一:把 where 先拿去做 select。 SELECT id, name FROM classes WHERE id > 2 看到一行「三班」, 确认这就是你想改的范围,再把同一段条件粘过去。 习惯二:where 1 = 0 空跑。 影响 0 行、什么都没改,但语句跑通了——列名写错、 类型不匹配这类问题会在这时候暴露。 MySQL 还有个 safe-updates 模式: 不写 where、或者 where 里没用主键/索引的 update 直接拒绝。 生产环境建议开, 它是最后一道防线。 本沙箱没有这个模式。

五、update 配 order by 与 limit:MySQL 有,这里没有

MySQL 支持 UPDATE … ORDER BY 列 LIMIT n——比如「只改最早的三条」。 这是 MySQL 的扩展,标准 SQL 里没有。 本沙箱不支持,limit 只能用在 select 上。 要在这边实现同样的效果,得先用子查询查出主键,再按主键改。 这个差异要留意:教材里出现 update … limit 的地方,在这边一律跑不了,得改写成两步。 同理 delete … limit 也一样。

六、动手 Demo:改一行、改多列、加库存,最后撞一次「忘了 where」

Demo 六段:基本写法、多列更新、用原值算新值、忘写 where、两条保命习惯、以及 limit 那个方言。 第四段是这一篇的重点——它会真的把三行全改掉,让你看清「没有报错」比「报错」危险得多。

import _q46 as Q
c = Q.con()

# 基本写法
Q.run(c, "UPDATE classes SET teacher = '孔老师(代)' WHERE id = 2")
# 一次改多列
Q.run(c, "UPDATE classes SET room = 'A101', teacher = '马老师' "
          "WHERE id = 1")
# 用原值算新值
Q.run(c, "UPDATE goods SET stock = stock + 10 WHERE cat = '配件'")
# 忘写 where:影响 3 行
c2 = Q.con()
Q.run(c2, "UPDATE classes SET teacher = '全部'")
# 空跑一次
Q.run(c2, "UPDATE classes SET teacher = '试运行' WHERE 1 = 0")
# 先 select 验证 where 范围
c2.execute("SELECT id, name FROM classes WHERE id > 2")

实跑输出:

--------------------------------------------------------------------
第三十三课:update,以及「忘了写 where」这一课
--------------------------------------------------------------------

  基本写法:set 改哪列,where 定范围
  SQL> SELECT id, name, teacher, room FROM classes WHERE id = 2
  id     name     teacher     room
  ------------------------------------
  2      二班     孔老师      A102
  (1 行)

  SQL> UPDATE classes SET teacher = '孔老师(代)' WHERE id = 2
  影响 1 行

  SQL> SELECT id, name, teacher, room FROM classes WHERE id = 2
  id     name     teacher          room
  -----------------------------------------
  2      二班     孔老师(代)     A102
  (1 行)

  **`update 表 set 列 = 值 where 条件`**——**`set` 说改什么,`where` 说改哪些行。** 两条都清楚,
  改动才可控。

  一次改多列:逗号隔开
  SQL> UPDATE classes SET room = 'A101', teacher = '马老师' WHERE id = 1
  影响 1 行

  **多列用逗号连起来写在同一个 `set` 后面**,只跑一次。
  **拆成两条 `update` 也能改,但那要扫两遍表,还可能中途失败只改了一半。**

  用原值算新值:这是 update 最好用的一招
  SQL> SELECT name, stock FROM goods WHERE cat = '配件' ORDER BY id
  name         stock
  -------------------
  跳绳         200
  护腕         300
  运动水壶     0
  瑜伽垫       60
  (4 行)

  SQL> UPDATE goods SET stock = stock + 10 WHERE cat = '配件'
  影响 4 行

  SQL> SELECT name, stock FROM goods WHERE cat = '配件' ORDER BY id
  name         stock
  -------------------
  跳绳         210
  护腕         310
  运动水壶     10
  瑜伽垫       70
  (4 行)

  **`stock = stock + 10` 里的 `stock` 指的是「这一行原来的值」**,所以每行各自加 10——200 变 210、
  0 变 10。
  **这一招做库存扣减、计数累加时天天用。** **但并发时要注意**:两个请求同时读到 200,各加 10,
  **最后可能只加到 210 而不是 220**,这叫丢失更新。 **第 18 章讲锁和事务时会回来看这个问题。**

  忘写 where 的后果
  SQL> SELECT COUNT(*) AS before_rows FROM classes WHERE teacher <> '全部'
  before_rows
  ------------
  3
  (1 行)

  SQL> UPDATE classes SET teacher = '全部'
  影响 3 行

  SQL> SELECT COUNT(*) AS changed FROM classes WHERE teacher = '全部'
  changed
  --------
  3
  (1 行)

  **影响 3 行,全班老师都被改成了「全部」。**
  **这就是没写 `where` 的结果——它不是错误,是「范围写成了整张表」。**
  **唯一的保险是习惯**:`update` 和 `delete` 打完,
  **先回头看一眼有没有 `where`,再看一眼条件对不对。**

  两条安全习惯:空跑与先查
  **习惯一,先把 `where` 拿去做 `select`。** 想知道「会改到哪几行」,
  先跑 `SELECT * FROM classes WHERE 条件`。 人对了,再把同一段 `where` 粘到 `update` 上。

  SQL> SELECT id, name FROM classes WHERE id > 2
  id     name
  ------------
  3      三班
  (1 行)

  **习惯二,用 `where 1 = 0` 空跑一次。** 影响 0 行、什么都没改,但语句本身跑通了,
  **能提前发现列名写错、类型不匹配这类问题。**

  SQL> UPDATE classes SET teacher = '试运行' WHERE 1 = 0
  影响 0 行

  update 配 order by 与 limit:MySQL 有,这里没有
  **MySQL 支持 `UPDATE ... ORDER BY 列 LIMIT n`**——比如「只改最早的三条」。
  **这是 MySQL 的扩展,标准 SQL 里没有。**
  **本沙箱不支持**,`limit` 只能用在 `select` 上。
  **要在这边实现同样的效果,得先用子查询查出主键,再按主键改。**
  **MySQL 还有个 `safe-updates` 模式**(客户端加 `--safe-updates` 开启):
  **不写 `where`、或者 `where` 里没用主键/索引的 `update` 直接拒绝执行。** **生产环境建议开**,
  它能挡住最蠢的那种事故。

第一段改一行。 二班老师加「(代)」,影响 1 行。 第二段改多列。 房间号和老师一起改,只跑一遍。 第三段加库存。 四件配件各加 10:200→210、300→310、0→10、60→70。 注意 0 变 10 那一行——0 + 10 也是 10,不是「0 加了还是 0」。 第四段忘写 where。 影响 3 行,全班老师变「全部」。 第五段两条习惯。 先 select 看到「三班」一行;再 where 1 = 0 空跑,影响 0 行。 第六段没有 SQL,讲的是 limit 那个方言和 safe-updates。

30 秒实验

  1. UPDATE goods SET stock = stock * 2 WHERE stock > 0 会影响几行?数值变成多少?
  2. UPDATE classes SET teacher = 'x' WHERE id = 99 影响几行?什么意思?
  3. 要把「库存为 0 的那行的 on_sale 改成 0」,怎么写?先空跑一遍。

常见误区

  1. 不写 where 就回车。 语法完全正确,所以不报错,但整张表都被覆盖,原来的值再也查不回来。
  2. 先 select 出来在程序里算好,再 update 写回去。 多一次往返,并发时还会丢更新。 直接用 SET 列 = 列 + 值。
  3. 以为「影响 0 行」是执行失败。 是条件没匹配上,该改条件,不是改语法。
自测三问(点开对答案) 1. `SET stock = stock + 10` 里的 `stock` 指什么? —— 指「这一行原来的值」,所以每行各自加 10,而不是统一变成某个值。 2. 为什么「忘了写 where」比「写错语法」更危险? —— 写错语法会立刻报错、什么都没改;忘写 `where` 语法正确、照样执行,整张表被覆盖且无法撤销。 3. `update ... limit` 在本沙箱能用吗? —— 不能,那是 MySQL 的扩展。要用得先查出主键再按主键改。

改完了,还剩「删」。下一篇 delete,以及两种删不掉、删不干净的情况。

相关笔记