DML 数据操作语言:INSERT、UPDATE、DELETE 的完整语法
以 players 排行榜表讲透 MySQL 写操作:多行 INSERT、INSERT SET、UPSERT 预告、事务防误操作、DELETE 与 TRUNCATE 的回滚代价与影响行数读法,附 1136/1048 真实报错调试。
前置知识
- DDL 数据定义语言:知道「表」是一格一格的数据结构;
- 一个能执行 SQL 的环境:装好的 MySQL 客户端,或本模块的 SQL Playground。
本文与 SQL 数据操作与查询 互为搭子:110 串工作流,本文讲全每条语法分支。统一案例 players 排行榜表(五列 id/name/score/level/updated_at)的建表脚本在 110 第 2 节,先复制执行再回来;没学过 DDL 也能读,建表只是「准备一张空表」。
学习目标
读完本文你将能够:
- 用单行、多行、
INSERT ... SET三种写法插入数据,说清没写的列拿到了什么; - 用一条 UPDATE 同时改多列、基于原值更新,并在事务保护下执行写操作;
- 说出 DELETE 与 TRUNCATE 在类别、回滚、自增编号、速度上的四个区别;
- 读懂
Rows matched与Changed的差异,发现「改了个寂寞」; - 修复
ERROR 1136、ERROR 1048,并把无 WHERE 事故扼杀在事务里。
预计 45 到 60 分钟,全程动手。
1. 你现在要解决什么问题
排行榜上线了,你是它的后端。今天的需求单长这样:
- 上午:新玩家「豆豆」「萌萌」注册,一个用常规写法,一个试试逐列赋值;
- 下午:运营提交了 Rain 的最新成绩,还要给零分新玩家发「新手礼包」,分数和等级一起改;
- 傍晚:玩家 Nova 注销账号,数据要删掉;
- 深夜:你困得手抖,把一条 UPDATE 的 WHERE 写丢了——五分钟内必须救回数据。
这四件事对应 DML(Data Manipulation Language,数据操作语言)的三条语句:INSERT、UPDATE、DELETE——难的不是语法,是「写对」和「写错了能救回来」。
2. 准备战场:把数据灌成标准状态
全新的表直接灌数据;练过旧数据的表先 TRUNCATE TABLE players; 清空(自增编号从 1 重新开始,它和 DELETE 的区别见第 5 节)。然后灌入 110 篇同款的五位玩家——这就是多行 INSERT:一个语句、多组值,组与组用逗号隔开:
INSERT INTO players (name, score, level) VALUES
('小明', 1250, 8),
('阿黄', 980, 6),
('Rain', 2410, 15),
('Nova', 760, 5),
('老K', 1520, 10);预期输出:
Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0用 SELECT id, name, score, level FROM players; 验证:五行依次是小明 1250/8、阿黄 980/6、Rain 2410/15、Nova 760/5、老K 1520/10。TRUNCATE 后 id 恰好 1 到 5;你的表若删过数据,id 会接续,示例里换成你查到的即可。
3. INSERT:把新行写进表
3.1 单行插入与「没写的列」
豆豆注册了,此刻只有名字:
INSERT INTO players (name) VALUES ('豆豆');
SELECT id, name, score, level FROM players WHERE name = '豆豆';预期:一行结果,id 6、score 0、level 1。没写的两列拿到了建表声明的默认值(NOT NULL DEFAULT,见 110 第 2 节)。INSERT 的规矩:列清单写了哪几列,VALUES 就给哪几列的值;没写的列交给默认值或 NULL。
3.2 INSERT … SET:逐列赋值
萌萌也注册了,换一种写法——每列一个等号,像给变量赋值:
INSERT INTO players SET name = '萌萌', score = 0;读起来直观,但只能插单行;批量场景一律回到多行写法。
3.3 撞车了怎么办:ON DUPLICATE KEY UPDATE 预告
假设你在写「存档同步」:客户端带着 id 反复上报成绩,普通 INSERT 第二次就会撞主键报 ERROR 1062。MySQL 提供一步到位的写法——撞了唯一键就转成更新:
INSERT INTO players (id, name, score, level) VALUES (3, 'Rain', 2600, 15)
ON DUPLICATE KEY UPDATE score = 2600, level = 15;预期输出 Query OK, 2 rows affected,查询 id = 3 可见 Rain 的分数被覆盖为 2600——撞键转更新时 MySQL 把「尝试插入 + 更新」记成 2 行。这叫 UPSERT;INSERT IGNORE、REPLACE 等家族对比见 MySQL 速查。
4. UPDATE:改数据,以及那条保命铁律
4.1 改单列:基于原值的表达式更新
Rain 又赢了一局加 300 分。注意「在原值上加」,不是先查出来算好再写回:
UPDATE players SET score = score + 300 WHERE id = 3;
SELECT name, score FROM players WHERE id = 3;预期:Rain | 2900。SET score = score + 300 右边的 score 读的是旧值,原地算好写回。先查后算再写回多一次往返,并发时后写还会覆盖先写。
4.2 改多列:新手礼包
运营活动:所有零分新玩家送 100 分、升 2 级。一条 UPDATE 用逗号分隔可同时改任意多列:
UPDATE players SET score = 100, level = 2 WHERE score = 0 AND level = 1;预期输出(豆豆、萌萌两人命中):
Query OK, 2 rows affected (0.01 sec)
Rows matched: 2 Changed: 2 Warnings: 0多条件用 AND/OR 连接,查询条件完整用法见 DQL。
4.3 保命铁律:无 WHERE 即全表
现在复现深夜事故。你本想给阿黄加 10 分,手一抖少打了半句:
START TRANSACTION;
UPDATE players SET score = score + 10;
SELECT name, score FROM players;
ROLLBACK;第一行输出是 Query OK, 7 rows affected——全表七个人都被加了 10 分。规则只有一句话:UPDATE 没有 WHERE,作用范围就是全表,DELETE 同理。
没出事是因为套了事务(transaction):START TRANSACTION 开始记账,修改只对你可见;ROLLBACK 整体反悔;COMMIT 确认落账。生产库写 UPDATE/DELETE 因此固定三步——开事务、验证、提交:
START TRANSACTION;
UPDATE players SET score = score + 10 WHERE id = 2;
SELECT name, score FROM players WHERE id = 2; -- 确认只有阿黄变成 990
COMMIT;本文只用事务「会回滚」这一层,隔离级别与锁的原理见 SQL 数据定义与高级对象 第 2 节。
5. DELETE 与 TRUNCATE:删数据的两种代价
5.1 按条件删除,并且能反悔
Nova 注销账号:
DELETE FROM players WHERE id = 4;
SELECT id, name FROM players; -- Nova 消失,剩 6 行预期:Query OK, 1 row affected。DELETE 与 UPDATE 规则完全一致:WHERE 决定删谁,没有 WHERE 就是清全表。它同样能被事务反悔,完整体验一次「删了又救回来」:
START TRANSACTION;
DELETE FROM players WHERE level = 2; -- 豆豆、萌萌
SELECT COUNT(*) FROM players; -- 5
ROLLBACK;
SELECT COUNT(*) FROM players; -- 7,全都回来了5.2 DELETE 与 TRUNCATE 的四个区别
清空整张表有两条路,代价完全不同:
| 维度 | DELETE FROM players | TRUNCATE TABLE players |
|---|---|---|
| 语言类别 | DML(数据操作) | DDL(数据定义) |
| 回滚 | 事务里可以 ROLLBACK | 隐式提交,不可回滚 |
| 自增编号 | 保留,新行接着旧编号 | 重置,从 1 重新开始 |
| 大表速度 | 逐行删、逐行记日志,慢 | 直接重建表,快得多 |
「不可回滚」不是吓唬人:DELETE 每删一行都要在 undo log 记一条「怎么撤销」,5.1 节的反悔才有可能,代价是日志多、速度慢;TRUNCATE 不做这份记录,所以快,也所以没有后悔药。语法细节见 DDL 数据定义语言。
5.3 把战场恢复成标准状态
执行下面的脚本让表回到标准状态——也是「测试数据脚本可重复执行」的示范:
TRUNCATE TABLE players;
-- 重新执行第 2 节的五行 INSERT,然后补上新注册的两人:
INSERT INTO players (name, score, level) VALUES ('豆豆', 100, 2), ('萌萌', 100, 2);6. 核心概念:读懂影响行数
每次写操作后客户端都会回话,学会读它就有了行车记录仪:
Query OK, N rows affected:真正被写动的行数;Records: 5 Duplicates: 0 Warnings: 0:多行 INSERT 专用——插入 5 组、0 组撞键、0 条警告;- UPDATE 还多一行
Rows matched: X Changed: Y:matched 是 WHERE 命中的行数,changed 是值真的变化的行数。两者可以不相等:UPDATE players SET score = score WHERE id = 3;得到 matched 1、changed 0——命中了但值本来就一样;matched 为 0 说明 WHERE 没命中,多半是条件写错。affected 远超预期?立刻想起 4.3 节。
7. 常见错误与调试实录
错误一:列数不匹配。多行 INSERT 时某组值少写了一个:
INSERT INTO players (name, score, level) VALUES
('阿豪', 300, 2),
('铁蛋', 500);真实报错:
ERROR 1136 (21S01): Column count doesn't match value count at row 2读报错三步:1136 直译「列数与值的个数不匹配」;at row 2 指第 2 组值——('铁蛋', 500) 只有 2 个值,对不上 3 列;补上即可。
错误二:非空列塞了 NULL:
INSERT INTO players (name, score) VALUES (NULL, 500);真实报错:
ERROR 1048 (23000): Column 'name' cannot be null报错把列名都指好了,来自建表时的 NOT NULL 约束——「让数据库替你挡脏数据」就是此刻。类似的还有 ERROR 1364(非空列又没给默认值),110 第 7 节演示过。
错误三:无 WHERE 全表更新,4.3 节复盘过,备忘一行:affected 远超预期,先 ROLLBACK,再找 WHERE。
8. 修改实验
- 把 3.1 节的注册语句改成插入「分数 50、等级 3」的玩家,先预测四列各是多少,运行验证;
- 把 4.2 节的新手礼包改成「等级小于 3 的玩家加 200 分、升 1 级」,先预测影响行数,事务里执行、确认 matched 后 COMMIT;
- 事务里
DELETE FROM players WHERE score = 0;,观察 COUNT(*) 前后变化,再 ROLLBACK 救回来。
9. 实际项目中的使用场景
- 注册、下单类写入:单行 INSERT,靠
NOT NULL DEFAULT兜底; - 初始化测试数据、批量导入:多行 INSERT 一次到位,配 TRUNCATE 做可重复的测试环境;
- 加分、扣款、库存扣减:永远写
score = score + 300这类基于原值的表达式,避免「先查后写」的并发覆盖; - 生产环境的 UPDATE/DELETE:事务三步走,宁可慢十秒不可裸奔;
- 玩家注销:想清楚硬删(DELETE)还是软删(加
is_deleted列打标记,数据可恢复)——真实系统九成选软删。
10. 小练习
预测题(不执行,先写答案):当前表处于 5.3 节的标准状态,执行
UPDATE players SET level = level + 1 WHERE score < 1000;客户端会报告几 rows affected?改的是哪几位玩家?写完答案再运行验证。
修改题:把 3.3 节的 UPSERT 改成「id = 5 的玩家设为 2000 分、12 级」,连跑两遍,验证第二遍不报错、结果不变。
修 Bug 题:下面的注册语句报 ERROR 1136,按读报错三步定位,再用「补值」或「减列」修复:
INSERT INTO players (name, score) VALUES ('阿豪', 300, 2);挑战题(不看提示自己写):写一段事务脚本删除「全表分数低于 500 的玩家」,删前先查询确认删的是谁,无误后提交。验收:脚本连跑两遍,第二遍 Rows matched 必须为 0。
11. 与之前和之后的知识的关系
- 往前:DDL 数据定义语言 建好的表和约束从本文起被喂进数据;SQL 数据定义与高级对象 第 2 节的事务详解是 4.3 节三步走的原理层;
- 往后:SQL 数据操作与查询 把三条语句和 SELECT 串成完整工作流;DQL 数据查询语言 教你把验证查询写得又快又准;
- 更远:跨表改数(UPDATE … JOIN、多表删除)要等 多表连接 学完 JOIN 再回来解锁。
12. 官方文档
- INSERT 语法(含 INSERT … SET):https://dev.mysql.com/doc/refman/8.4/en/insert.html
- UPDATE 语法:https://dev.mysql.com/doc/refman/8.4/en/update.html
- DELETE 语法:https://dev.mysql.com/doc/refman/8.4/en/delete.html
- 事务(COMMIT/ROLLBACK):https://dev.mysql.com/doc/refman/8.4/en/commit.html
- 服务端错误码总表:https://dev.mysql.com/doc/mysql-errors/8.4/en/server-error-reference.html
13. 自我检查
- 能不看资料写出单行、多行、SET 三种 INSERT,说出没写的列会拿到什么;
- 能背出保命三步,并现场用 ROLLBACK 救一次全表事故;
- 能对照表格说清 DELETE 与 TRUNCATE 的四个区别;
- 拿到 1136 与 1048 报错,各自知道第一步看哪里。
本章总结
DML 三件套的语法一天就能记全,值钱的是三个习惯:写操作先想 WHERE(没有 WHERE 就是全表);先开事务(ROLLBACK 是唯一的后悔药,TRUNCATE 连后悔药都不给);写完立刻读影响行数。INSERT 进、UPDATE 改、DELETE 删,加上 SELECT,排行榜后端四类操作就齐了。
下一步
进入 SQL 数据操作与查询 把写法和查询串成完整工作流;之后 DQL 数据查询语言 把 SELECT 的每个子句讲透。