delete from 表 where 条件,删的是「行」不是「列」。 不写 where 就是清空整表。
两种「删不掉」:外键会拦住(一班的四个学生指着它,
报 FOREIGN KEY constraint failed);truncate 在本沙箱没有,那是 MySQL 的语句。
删了自增值不退回去(删掉 id 4,下一个新行拿到 5)。
软删除是把「删」变成「标记」:加一列 deleted,改数据不改结构。
上一篇改,这一篇删——三类语句里后果最重的一种。
update 至少还留着行,delete 之后连行都没了。
而且平时觉得「删了就删了」,真出事时往往要的是「那一行原来是什么」。
一、基本写法:删的是行,不是列
delete from 表 where 条件。 Demo 里先插一个「四班」再删掉它,影响 1 行。
为什么挑四班删:原来三个班都有学生指着,
删不动——下一段就撞上。 这也说明「删一行」这件事,并不总是能成。
delete 后面不能跟列名。 删列是 alter table … drop column,
那是改表结构,不是删数据。 这两件事经常被混在一起问。
二、删不掉之一:外键拦着
DELETE FROM classes WHERE id = 1 报 FOREIGN KEY constraint failed。
因为 students 表里有四个学生的 class_id 是 1。
MySQL 那边的报错是 Cannot delete or update a parent row,理由一样。
这就是外键的价值:它不让你留下「指向不存在的班」的学生。
要真想删,两条路:先把那些学生的 class_id 改到别的班,
或者先删掉那些学生。 两条路都得你自己决定数据怎么处理,数据库不替你猜。
三、删不掉之二:truncate 这里没有
delete from 表(不带 where)清空整表,但是逐行删的,每删一行都要记日志,大表很慢。
truncate table 表 是「把表推倒重建」,快得多,
但不可回滚、也不触发触发器。 这是 MySQL 的语句。
本沙箱没有 truncate,报 near "TRUNCATE": syntax error。
在这边清空只能 DELETE FROM 表。
注意清空也受外键约束:classes 有子表指着,
直接清空会被拦,得先清子表再清父表。
truncate 在 MySQL 里倒是会直接失败(有外键引用时不允许)。
四、删了以后,自增值不会退回去
删掉 id = 4,再插一行,拿到的是 5,不是 4。 自增值只增不减,删掉的行不会「让号」。
这是刻意设计的:如果复用刚删掉的 id,
别的地方还留着的旧引用(日志、订单快照、外部系统)就会突然指向一个新对象。
那比空号危险得多。
truncate 会重置自增值(相当于重建表),delete 不会——这一条是两者最实际的区别,
也是「我删了表为什么 id 从 1000 开始」的答案。
五、软删除:把「删」变成「标记」
做法:给表加一列 deleted(默认 0),
「删除」时改成 1,查询时统一加 WHERE deleted = 0。 Demo 里把孙悦那行标成 1,全表还是 12 行,
未删的 11 行。
三个好处:能恢复(改回 0 就行)、历史订单还能查到是谁买的、外键不会断。
一个代价:所有查询都得记得带那个条件,
漏一处,删掉的数据就冒出来了。 常见做法是建一个「只看未删」的视图,让业务层只查视图。
第 14 章讲视图时会回来看这个用法。
六、动手 Demo:删一行、被外键拦、自增不退、软删除
Demo 五段:删一行看效果,撞一次外键,撞一次 truncate,验证自增值不退回,最后做一次软删除。
中间两段都是「删不掉」——它们说明了「删除」在真实库里从来不是自由动作。
import _q46 as Q
c = Q.con()
# 先插一个没有学生的班,再删它
Q.run(c, "INSERT INTO classes (name, teacher, room) "
"VALUES ('四班', '唐老师', 'B301')")
Q.run(c, "DELETE FROM classes WHERE id = 4")
# 删有学生的班:外键拦住
c.execute("DELETE FROM classes WHERE id = 1")
# truncate:本沙箱没有
c.execute("TRUNCATE TABLE classes")
# 自增值不退回
c3 = Q.con()
Q.run(c3, "INSERT INTO classes (name, teacher) VALUES ('四班', '唐老师')")
Q.run(c3, "DELETE FROM classes WHERE id = 4")
Q.run(c3, "INSERT INTO classes (name, teacher) VALUES ('五班', '何老师')")
# 软删除:加一列当标记
c4 = Q.con()
Q.run(c4, "ALTER TABLE students ADD COLUMN deleted INTEGER "
"NOT NULL DEFAULT 0")
Q.run(c4, "UPDATE students SET deleted = 1 WHERE id = 12")
实跑输出:
--------------------------------------------------------------------
第三十四课:delete,以及「删不掉的」和「删不干净的」
--------------------------------------------------------------------
基本写法:where 定范围
SQL> SELECT id, name FROM classes ORDER BY id
id name
------------
1 一班
2 二班
3 三班
(3 行)
SQL> INSERT INTO classes (name, teacher, room) VALUES ('四班', '唐老师', 'B301')
影响 1 行
SQL> DELETE FROM classes WHERE id = 4
影响 1 行
SQL> SELECT id, name FROM classes ORDER BY id
id name
------------
1 一班
2 二班
3 三班
(3 行)
**先插了个「四班」,再把它删掉,影响 1 行。**
**为什么挑它:原来三个班都有学生指着,删不动(下一段就撞)。**
**`delete from 表 where 条件`**,删的是「行」。
**`delete` 后面不能跟列名**——它是删行,不是删列。
外键会拦住你:删不掉的班
SQL> DELETE FROM classes WHERE id = 1
报错:FOREIGN KEY constraint failed
**一班的四个学生还在 `students` 表里指着它,所以删不掉。**
**MySQL 那边的报错是 `Cannot delete or update a parent row`,理由一样。**
**这就是外键的价值**:
**它不让你留下「指向不存在的班」的学生。**
**要真想删,得先把那些学生的 `class_id` 改掉或者删掉。**
delete 与 truncate:两个都清空,代价不同
**`delete from 表`(不带 `where`)清空整表,但它是逐行删的。**
**`truncate table 表` 是「把表推倒重建」,快得多,但不可回滚、也不触发触发器。**
**本沙箱没有 `truncate`**,教材原码在 MySQL 上跑:`TRUNCATE TABLE classes`。
报错:near "TRUNCATE": syntax error
**在这边要清空,只能 `DELETE FROM 表`。** **注意 `classes` 有外键指着,直接清空也会被拦**——
**得先清子表再清父表。**
删了以后,自增值不会退回去
SQL> INSERT INTO classes (name, teacher) VALUES ('四班', '唐老师')
影响 1 行
SQL> SELECT MAX(id) AS max_id FROM classes
max_id
-------
4
(1 行)
SQL> DELETE FROM classes WHERE id = 4
影响 1 行
SQL> INSERT INTO classes (name, teacher) VALUES ('五班', '何老师')
影响 1 行
SQL> SELECT id, name FROM classes ORDER BY id
id name
------------
1 一班
2 二班
3 三班
4 五班
(4 行)
**新插入的那行拿到的是 5,不是 4。** **自增值只增不减,删掉的行不会「让号」。**
**这是刻意设计的**:如果复用刚删掉的 id,**别的地方还留着的旧引用就会突然指向一个新对象**,
那比空号危险得多。
**`truncate` 会重置自增值**(相当于重建表),**`delete` 不会**——**这一条也是两者最实际的区别。**
软删除:把「删」变成「标记」
SQL> ALTER TABLE students ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0
完成
SQL> UPDATE students SET deleted = 1 WHERE id = 12
影响 1 行
SQL> SELECT COUNT(*) AS all_rows FROM students
all_rows
---------
12
(1 行)
SQL> SELECT COUNT(*) AS alive FROM students WHERE deleted = 0
alive
------
11
(1 行)
**孙悦那行还在表里,只是被标了 `deleted = 1`。**
**查询时统一加 `where deleted = 0`,看上去就是「删掉了」。**
**软删除的三个好处**:**能恢复**、**历史订单还能查到是谁买的**、**外键不会断**。
**代价是所有查询都得记得带那个条件**——
**漏一处,删掉的数据就冒出来了。** **常见做法是建一个「只看未删」的视图,让业务层只查视图。**
第一段删一行。 先插四班、再删四班,影响 1 行,表回到三条。
第二段撞外键。 删一班报 FOREIGN KEY constraint failed。
第三段撞方言。 TRUNCATE 在这里不存在,报语法错。
第四段自增值。 删掉 id 4 之后新插的那行是 5——号没退回去。
第五段软删除。 加一列 deleted,把孙悦标成 1;全表 12 行,未删 11 行。
30 秒实验
- 删掉
students里 id = 12 的那行,能成功吗?为什么? DELETE FROM order_detail与DELETE FROM orders谁该先执行?- 软删除之后,
SELECT COUNT(*) FROM students给的是几?为什么?
常见误区
delete不写where。 那是清空整表,而且逐行删、很慢,删完还没有撤销。- 以为自增值会补上空号。 删掉 4 之后新行是 5,不是 4;
要重置只能
truncate(MySQL)或者重建表。 - 软删除之后忘了带
deleted = 0。 漏一处,删掉的数据就出现在页面上。
自测三问(点开对答案)
1. `delete` 和 `truncate` 有什么区别? —— `delete` 逐行删、可回滚、不重置自增值;`truncate` 重建表、快、不可回滚、重置自增值。 本沙箱没有 `truncate`。 2. 为什么删一班会被拦?怎么才能删掉? —— `students` 里有四个学生指向它,外键拦住了。要先把那些学生的 `class_id` 改掉或删掉那些学生。 3. 软删除的代价是什么? —— 所有查询都得带 `WHERE deleted = 0`,漏一处删掉的数据就会冒出来。数据这块讲完了:增、改、删都有了。 下一篇换个层级——不再动数据,动「表」本身:建库、建表、改表、删表。
- ☐ (本篇完,下一篇 8.1 表的操作简介)