INSERT 一定要写列名:不写就依赖列顺序,表一加列这条语句就废了。
UPDATE 和 DELETE 忘写 WHERE,影响的是全表;演示里一次误操作把平均分从 80 拉到 0。
DELETE 逐行记日志能回滚,TRUNCATE 只记页的释放所以快,但不支持 WHERE。
表建好了,接下来就是往里放数据、改数据、删数据。
这一节是三对关键字:INSERT、UPDATE、DELETE。
教材 5.3 的三小节按这个顺序排,
每一节都配了「在 SSMS 里操作」和「用语句操作」两种做法。实际项目里只用语句——
界面点一行改一行,一万行要点到什么时候。
演示里真跑了一次「不带 WHERE 的 UPDATE」,平均分从 80 直接掉到 0,
再用 SAVEPOINT 回滚回来。这一段的目的不是教语法,是让你看见代价。
一、INSERT:三种写法,差在速度
| 写法 | 长什么样 | 什么时候用 |
|---|---|---|
| 单行 | VALUES (值1, 值2) | 手工补一条 |
| 多行 | VALUES (...), (...), (...) | 一次插几行 |
| 插入查询 | INSERT INTO ... SELECT | 从别的表搬 |
单行和多行的差别不只是写法,是往返次数。 每条 INSERT 一次网络往返、一次事务提交;
一次插一千行,往返一千次。这就是为什么批量导数据要拼多行 VALUES。
INSERT INTO 表 SELECT ... 是从别的表搬数据。 它不写 VALUES,值来自那条 SELECT。
列数、类型、顺序都要对上,对不上报的是「列名或数目与表定义不匹配」。
一定要写列名。 INSERT INTO tb VALUES (...) 依赖列的定义顺序——表一加列,这条语句要么报错,
要么把值插到错误的列里。后者更危险,因为不报错。
二、UPDATE:忘写 WHERE 的代价
演示里跑了一次:先正常加 5 分,再执行一次不带 WHERE 的 UPDATE,
平均分从 80 出头直接变成 0,因为全表的分数都被改成了 0。
UPDATE tb_score SET score = score + 5 WHERE sno = '2023001' -- 影响 3 行
UPDATE tb_score SET score = 0 -- 影响全表
两行只差一个 WHERE,后果差一个量级。 而且第二行不会有任何提示——
SSMS 只会告诉你「3 行受影响」或者「6 行受影响」,不会问「你确定要改全表吗」。
所以生产库上的操作顺序是:先用同样的 WHERE 跑一遍 SELECT COUNT(*),
数对了再改成 UPDATE。 这一步只花三秒,能省掉一次事故。
演示里能回滚,是因为我加了 SAVEPOINT。 真在 SSMS 里点「执行」,
如果没开显式事务(BEGIN TRAN),那一秒就自动提交了——回滚不了。
还有一个更隐蔽的坑:UPDATE 里用子查询。 比如「把每个学生的总分更新到学生表」,
子查询写错关联条件,结果可能是「全表被更新成同一个值」,而且不报错。
三、DELETE 与 TRUNCATE
| 对比项 | DELETE | TRUNCATE |
|---|---|---|
| 删什么 | 符合条件的行 | 所有行 |
WHERE |
支持 | 不支持 |
| 记日志 | 每行都记 | 只记页的释放 |
| 自增列 | 不重置 | 重置成种子值 |
| 有外键引用 | 可以删 | 不能删 |
DELETE 的日志开销是它的代价,也是它的保险。 因为每行都记了,所以能回滚;
TRUNCATE 只记「这几页被释放了」,快得多,但你不能只删一部分。
TRUNCATE 会重置自增列。 清空之后下一条插入的 id 又回到 1。
如果业务上靠 id 唯一性做判断(比如订单号),这一步会让历史订单号被复用——很危险。
SQLite 没有 TRUNCATE。 等效做法是 DELETE FROM 表 不带 WHERE,
但它的行为跟 DELETE 一样,不会重置自增列。这是两者的一处实际差异。
四、三种语句的影响范围对照
| 语句 | 改什么 | 最怕的失误 |
|---|---|---|
| INSERT | 加行 | 不写列名,值串位 |
| UPDATE | 改行 | 忘 WHERE,全表被改 |
| DELETE | 删行 | 忘 WHERE,全表被删 |
三种语句的风险不对等。 INSERT 出错通常报错(列数不匹配),顶多插错一行;
UPDATE 和 DELETE 出错不报错,而且一次影响全表。
DELETE 比 UPDATE 更狠。 改错了还能再改回来(如果记得原值),删错了要么有备份、要么就没了。
所以生产库上删数据,先 SELECT 一遍。
这三条语句合起来叫 DML(数据操作语言),跟 CREATE/ALTER/DROP(DDL)是两类。区别在于:
DML 能回滚,DDL 在很多情况下不能。这也是为什么删表比删行危险。
五、动手 Demo:一次不带 WHERE 的 UPDATE
这一段建一张成绩表,用三种写法插六行,然后正常改一次、再故意做一次不带 WHERE 的改,
看平均分怎么从 80 掉到 0,最后用 SAVEPOINT 回滚回来。
import sqlite3
c = sqlite3.connect(u":memory:")
c.execute(u"CREATE TABLE tb_score (id INTEGER PRIMARY KEY AUTOINCREMENT,"
u" sno TEXT NOT NULL, subject TEXT NOT NULL, score INT)")
c.executemany(u"INSERT INTO tb_score (sno, subject, score) VALUES (?,?,?)",
[('2023001', '语文', 88), ('2023001', '数学', 92),
('2023001', '英语', 79), ('2023002', '语文', 95),
('2023002', '数学', 61), ('2023003', '语文', 73)])
# 正常的 UPDATE,带 WHERE
c.execute(u"UPDATE tb_score SET score = score + 5 WHERE sno = '2023001'")
print(u"平均分 %.2f" % c.execute(u"SELECT AVG(score) FROM tb_score").fetchone()[0])
# 故意忘写 WHERE
c.execute(u"SAVEPOINT sp1")
c.execute(u"UPDATE tb_score SET score = 0")
print(u"忘写 WHERE 之后平均分 %.2f"
% c.execute(u"SELECT AVG(score) FROM tb_score").fetchone()[0])
c.execute(u"ROLLBACK TO sp1")
print(u"回滚之后平均分 %.2f"
% c.execute(u"SELECT AVG(score) FROM tb_score").fetchone()[0])
实跑输出:
--------------------------------------------------------------------
INSERT / UPDATE / DELETE
--------------------------------------------------------------------
① 三种插入写法
--------------------------------------------------------------------
单行插两次、批量插一次,一共 6 行
批量插比逐条插快得多:一次网络往返、一次事务提交。
教材里那种一条一条的 INSERT,实际项目里基本看不到——几万条要跑几分钟。
② UPDATE 忘写 WHERE 会怎样
--------------------------------------------------------------------
加了 WHERE,影响 3 行(2023001 的三科都加了 5 分)
现在 85 分以上的有 3 行
再执行一次不带 WHERE 的 UPDATE:
平均分从 83.83 变成 0.00,改动行数 6
回滚之后平均分回到 83.83
这就是为什么生产库上跑 UPDATE 要先 SELECT 一遍同样的 WHERE。
演示里能回滚,是因为我加了 SAVEPOINT。真在 SSMS 里点了「执行」,
没开显式事务的话,那一秒就提交了——回滚不了。
③ DELETE 和 TRUNCATE 的差别
--------------------------------------------------------------------
| 对比项 | DELETE | TRUNCATE |
|---|---|---|
| 删什么 | 符合条件的行 | 所有行 |
| WHERE | 支持 | 不支持 |
| 记日志 | 每行都记 | 只记页的释放 |
| 能回滚吗 | 能 | 能,但更快 |
| 自增列 | 不重置 | 重置成种子值 |
DELETE 掉 70 分以下的,剩 5 行
DELETE 掉全部,剩 0 行,表还在
TRUNCATE 在 SQLite 里没有对应语句,等效做法是 DELETE FROM 不带 WHERE。
SQL Server 的 TRUNCATE 快,是因为它不逐行记日志,只记「这几页被释放了」。
代价是它不能带 WHERE,也不能在有外键引用的时候用。
④ 三种语句的影响范围
--------------------------------------------------------------------
| 语句 | 改什么 | 最怕的失误 |
|---|---|---|
| INSERT | 加行 | 列顺序写错,值串位 |
| UPDATE | 改行 | 忘写 WHERE,全表被改 |
| DELETE | 删行 | 忘写 WHERE,全表被删 |
INSERT 的坑是「不写列名」——INSERT INTO tb VALUES (...) 依赖列顺序,
表结构一改(加了一列),这条语句就废了,而且报的错未必指向真正的原因。
UPDATE 和 DELETE 的坑是同一个:忘写 WHERE。这两个的代价不对等,DELETE 更狠。
第一段六个值里三行是同一个学生的三科——这就是「一对多」在数据上的样子,学生表里一条记录,
成绩表里三条。
第二段的平均分是 83.83。 六个分数是 93、97、84、95、61、73,加起来 503,除 6 得 83.83。
这个数不重要,重要的是它下面那个 0。
第三段就是这一节要演的事:一条不带 WHERE 的 UPDATE,把全表 6 行都改成了 0,平均分变 0。
而 SQLite 只回了一句「6 行受影响」,没有任何警告。
第四段的回滚能成功,全靠那个 SAVEPOINT。 真在 SSMS 里点执行、没开 BEGIN TRAN,
那一秒就自动提交了——这就是为什么生产库上要先 SELECT 再 UPDATE。
30 秒实验
- 建一张测试表,插十行,然后执行
UPDATE 表名 SET 某列 = 某值(不带WHERE), 看 SSMS 报「10 行受影响」的时候,你会不会有一秒的迟疑。 - 写一条
INSERT INTO 表名 VALUES (...)(不写列名),然后给表加一列,再跑一次。 看它报什么错——这就是「一定要写列名」的理由。 - 想一想:为什么
TRUNCATE比DELETE快,却不能用在大表的部分删除上? 因为它不逐行记日志,也不支持WHERE。 它只能「整表清空」这一种用法。
常见误区
- 「
UPDATE之前不用SELECT,直接改就行」。改错一次可能就找不回来了。 先用同样的WHERE跑SELECT COUNT(*),数对了再改成UPDATE。 这三秒钟是性价比最高的一步。 - 「
DELETE和TRUNCATE都能删数据,随便用哪个」。TRUNCATE会重置自增列、不能带WHERE、不能有外键引用。用错场景的后果不是慢, 是「删多了」或者「id 被复用」。 - 「
INSERT不写列名省事」。表一加列,这条语句要么报错、要么把值插错列。 后者不报错,更难发现。写列名多打十几个字,换的是「表结构改了这句还能跑」。
自测三问(点开对答案)
1. `UPDATE` 忘写 `WHERE` 会怎样? —— **全表的行都会被改。** 而且不会有任何警告,只告诉你「N 行受影响」。 **如果没开显式事务,这一秒就提交了,回滚不了。** 所以先 `SELECT` 数一遍行数。 2. `TRUNCATE` 和 `DELETE` 三个关键区别是什么? —— **一、`TRUNCATE` 不能带 `WHERE`;二、它重置自增列;三、它不能用在有外键引用的表上。** 快是它的优点,但这三条限制决定了它只能用于「清空整表」。 3. 为什么 `INSERT` 一定要写列名? —— **因为不写就依赖列的定义顺序。** 表一加列,原来的语句要么报「列数不匹配」, 要么把值插进错误的列——**后者不报错,数据就悄悄错了。**下一篇 5.4 讲约束。
前面几节都在讲「怎么写」,这一节讲「怎么不许写坏数据」——主键、外键、CHECK、DEFAULT、
UNIQUE 五种,各有各的拦法。
- ☐ (本篇完,下一篇 5.4 创建、删除和修改约束)