1. 认识DML:增删改到底是什么,为什么它才是数据库操作的主战场
先说个可能颠覆认知的结论:很多初学者学MySQL时,把大量精力花在DDL(建表语句)上,觉得创建表、修改字段才是重点。但真实项目里,一天里你写的最多的语句,一定是INSERT、UPDATE、DELETE这三兄弟。你写的CRUD接口、用户下单、修改订单状态、删除缓存数据,落到数据库层面全都是DML操作。
DML全称是Data Manipulation Language,数据操纵语言,核心就是三个关键字:INSERT(增)、UPDATE(改)、DELETE(删)。注意,SELECT(查)严格来说不算DML,它属于DQL(Data Query Language)。MySQL官方手册里把SELECT单列一类,但我们平时说“增删改查”,习惯把查也挂在嘴边。这一章我重点讲增、删、改,因为查询的内容太多了,值得单独开一篇。你只要记住一个原则:DML操作的是表里的数据,不会动表结构本身。你想给表加个字段,那是DDL的事,别混在一起。
为什么我说DML才是主战场?因为数据是流动的。用户在注册页面提交表单,后端收到请求先校验参数,然后往user表里INSERT一条记录;用户改了昵称,UPDATE一下;用户注销账号,DELETE或者标记删除。整个业务系统跑起来,每个环节都在产生DML操作。有人统计过,一个典型的电商系统,读多写少是常态,但写操作恰恰是保证数据准确性的关键。读操作出错了顶多是显示不对,写操作一错,数据就脏了,后面查出来全是错的,越跑越偏。
还有一个容易被忽略的点:DML操作是要走事务的。MySQL的InnoDB引擎默认每条DML语句自带事务,但如果多条写操作要一起生效,就得手动开启事务。比如转账功能:A账户扣钱、B账户加钱,两步必须同时成功或同时失败,不能只扣不加。这就是事务要解决的原子性问题。学DML的时候如果不同时建立事务思维,后面写业务代码很容易埋雷。
这一章适合谁看?已经能建库建表、熟悉MySQL基本命令的初学者,以及写了一段时间SQL但没系统整理过增删改细节的朋友。我会从最基础的语法讲到多表操作、性能注意事项、踩坑实录,全程用我实操过的场景做例子。学完之后,你写增删改语句应该能做到“不查手册、不看报错也能一次写对”,至少知道自己写的语句会带来什么影响。
2. 插入数据:INSERT的四种写法与踩坑现场
2.1 最基础的INSERT INTO ... VALUES
先看所有教材都会讲的标准写法:
INSERT INTO student (name, age, class_id, create_time) VALUES ('张三', 20, 1, '2025-01-15 14:30:00');这里有两个细节初学者经常搞混。第一,表名后面括号里是列名列表,这段可以省略,省略的话就默认把所有列都写上,必须和表结构完全一致。我强烈建议你永远不要省略列名。为什么?因为表结构总会变。今天表里有5列,你写INSERT INTO student VALUES (1, '张三', 20, ...),写的时候刚刚好。明天别人给表加了一个remark字段,你的语句立刻报错:Column count doesn't match value count at row 1。但是你显式写列名,加字段根本不影响你,顶多是新字段用默认值。
第二,VALUES后面括号里是值列表,顺序必须和前面的列名一一对应。这个规律很好记:列名怎么排,值就怎么排。字符串和日期要加单引号,数字可以直接写。NULL表示空值,如果某列有默认值,你不想插入数据,可以直接省略这列或者写成DEFAULT。
2.2 一次插多行:批量插入的效率差异
真实业务不可能一行一行地插。用户批量导入、订单批量同步,一次可能几百上千条。这时候用多行VALUES:
INSERT INTO student (name, age, class_id) VALUES ('李四', 21, 2), ('王五', 19, 3), ('赵六', 22, 2);多行插入不是图省事,而是数量级的效率提升。网上经常看到有人说“一次插1000行比循环插1000次快100倍”,这话不严谨,但方向上没错。有个很经典的比喻:每执行一条INSERT,数据库都要做一遍语法解析、权限检查、执行计划生成、事务日志写入。就像你寄快递,哪怕每件包裹都只有一公里远,你也要填1000次快递单、跑1000趟驿站。而批量插入等于一个快递单打包1000件包裹,虽然包裹本身没变,但流程和油费省了一大截。
实测下来,在我本地电脑的MySQL 8.0上,单条插入1000条记录大概耗时1.2秒,而批量插入10条一组、每次100条,1000条总耗时约0.08秒,快了十几倍。原因是批量插入减少了网络往返和日志刷盘次数。注意,批量插入的数据量也别太大,一次一万行以上会让binlog和事务日志压力骤增,还可能锁很多行。我的建议是单次控制在500到1000行,拆分批处理,兼顾速度和资源占用。
2.3 INSERT ... SET:另一种顺手写法
除了VALUES,MySQL还支持SET写法:
INSERT INTO student SET name = '孙七', age = 18, class_id = 4;这种写法最大的优点是可读性强,列和值用等号配对,一眼能看出哪个字段被赋了什么值;但它的缺点是不支持一次插多行。所以实际开发中,这种写法用得不算多,主要出现在存储过程里或者临时调试时。写代码的时候为了统一风格,还是建议把VALUES作为主力。
2.4 从一张表复制数据:INSERT ... SELECT
最容易被忽略的用法是胯表复制。例如要根据老表数据生成新表记录:
INSERT INTO student_2025 (name, age, class_id) SELECT name, age, class_id FROM student WHERE create_time >= '2025-01-01';这个写法我愿称之为“数据搬家神器”。做报表、做数据清洗、把在线库数据同步到分析库,都会用到。它的执行逻辑是先用SELECT查出数据,再逐条INSERT到目标表。要注意两点:一是SELECT出来的列数和类型要和目标表的列匹配,这点和VALUES一样;二是如果目标表有自增主键,复制时千万别把源表的主键一起复制过去,除非你就是想保留主键,否则会让目标表的自增值乱掉。
2.5 自增主键的坑:别乱插ID
自增主键是很多MySQL表的标准配置。它最大的好处是天然有序、索引效率高。但有三种坑我必须提醒你。
第一,显式插入一个超过当前自增值的ID,会导致后续自增值跳变。比如你表里最大ID是100,你手贱插入一条ID=2000的记录,下一次正常插入时自增会直接跳到2001,而不是101。如果你没有清理习惯,这个数字会越来越大。
第二,事务回滚后自增也不回退。InnoDB的自增是申请后就分配,哪怕事务回滚了,这个ID也不会重新用,因为InnoDB不会去维护“已用但失败”的ID列表。所以看到ID中间有空洞,不要慌,这不是数据丢了,是自增的正常表现。
第三,REPLACE INTO会影响自增。REPLACE是先删后插,如果插入时没有指定ID,它会找一条与唯一键冲突的记录删掉,再用新ID插入,会让ID增长更快。如果不需要这种“替换”语义,建议还是用INSERT加上ON DUPLICATE KEY UPDATE语法,至少更新时不会无谓地消耗自增ID。
3. 更新数据:UPDATE的正确打开方式,以及误更新如何自救
3.1 UPDATE基本语法与高危红线
UPDATE的语法框架很简单:
UPDATE student SET age = 21, update_time = NOW() WHERE id = 5;重点是:SET后面是赋值操作,WHERE决定要改哪些行。WHERE是UPDATE的高危红线。写UPDATE时,先问自己一句:这个WHERE能命中我真正想改的那几条记录吗?如果WHERE子句写错,或者干脆忘了写,那就是全表更新。这个教训我见过太多次了。曾经有个同事在测试环境执行UPDATE user SET status = 1,没加WHERE,把线上用户状态全改了。还好是测试库,但任何时候养成“条件先行”的习惯都不亏。
验证WHERE是否正确有一个土办法:执行UPDATE之前,先把语句改成SELECT,看看会查出哪些行。
-- 先执行,确认影响范围 SELECT id, name FROM student WHERE age > 20; -- 确认无误后再执行 UPDATE student SET age = 21 WHERE age > 20;这个习惯看起来多敲了几行代码,但省下来的可能是几个小时甚至几天的恢复成本。在MySQL里你还可以在执行UPDATE时用LIMIT限制更新行数,作为失控时的安全兜底,但不是所有场景都支持,最好还是靠严谨的WHERE来保证。
3.2 更新操作里的表达式运算
UPDATE不仅能直接赋固定值,还能在现有值基础上计算。最常见的例子是把商品价格统一涨10%:
UPDATE product SET price = price * 1.1 WHERE category_id = 3;注意这里price = price * 1.1,SQL在执行时先读取每一行的当前price值,乘1.1后再写回去。这和高年级编程语言里的“变量自增”是同一个思路。在MySQL中还可以在SET里同时更新多个字段,例如UPDATE t SET score = score + 5, times = times + 1 WHERE id = 1,这两个更新会基于同一时间点的快照执行,不用担心先后顺序的问题。
这里有个隐藏性能细节:如果WHERE条件用到的列上有索引,UPDATE先走索引定位到目标行,再去改数据,速度快很多。如果没有索引,那就是全表扫描,一行一行匹配,数据量一大就成了灾难。所以WHERE条件里的列尽量加索引,尤其是在高频更新的表上。这个原则在DELETE里同样适用。
3.3 多表更新:UPDATE JOIN的两种写法
业务里经常要按另一张表的条件来更新。例如根据教师表的评分,来更新学生表的综合等级:
UPDATE student s JOIN teacher t ON s.teacher_id = t.id SET s.grade_level = 'A' WHERE t.score >= 90;还有更接近自然语言的写法:
UPDATE student s, teacher t SET s.grade_level = 'A' WHERE s.teacher_id = t.id AND t.score >= 90;两种写法都能实现,区别在于是用显式JOIN还是用逗号连接加WHERE。我建议优先用第一种UPDATE ... JOIN ... ON的写法,因为连接条件在ON里写得清楚,不容易和过滤条件混在一起。多表更新最怕的坑是笛卡尔积:如果忘记写表之间的关联条件,就会把一个表的每一行都和另一表所有行配对,更新范围瞬间爆炸。比如学生表1000行、教师表50行,不加关联条件的话,可能更新50000行,那时候就哭都来不及了。
3.4 误更新之后的急救措施
万一真把UPDATE写错了、铺天盖地改了一片数据,怎么办?最有效的止损方式是立刻从备份恢复,但备份可能不是实时的。如果你的表或者整个库开启了binlog,而且配置了row格式,就能根据binlog回放把被修改的行恢复到之前的状态。具体做法是先把误操作的时间点确定,然后用mysqlbinlog工具解析出那段时间段的SQL,提取出误更新之前的数据形态,反向生成UPDATE或者重新INSERT。这个过程不轻松,但确实能救命。
没有binlog怎么办?只能看有没有其他备份、有没有数据库层面的事务快照。所以备份意识和binlog开启意识,必须从第一天写DML语句时就在脑子里生根。写了这么多年SQL,我真心建议你:生产环境千万不要关闭binlog,这是你最重要的一道保险。
4. 删除数据:DELETE与TRUNCATE,选错工具等于烧掉退路
4.1 DELETE的两种终极形态
DELETE是删除行的操作,基本语法为:
DELETE FROM student WHERE id = 10;执行后目标行从表里消失。注意,DELETE只是逻辑删除行为,底层InnoDB不会立刻把磁盘空间归还操作系统,而是标记这些记录为删除状态,空间留给后续新数据复用。所以你会发现刚删完几百万行数据,表的物理文件大小变化不大。这个概念对理解MySQL的碎片化和空间占用非常重要。
如果你不加WHERE条件,写的是:
DELETE FROM student;这和上面那个安慰剂版本有本质区别。不带WHERE的DELETE是清空整张表的数据,但保留了表结构、索引定义、自增值设置。很多人以为这和TRUNCATE差不多,一开始我也这么想,后来深入研究才发现完全两码事。
4.2 DELETE与TRUNCATE的关键差异
TRUNCATE的语法是TRUNCATE TABLE student,它也删除全部行,但实现方式和DELETE完全不同。
DELETE是逐行删除,期间会产生大量事务日志,每条删除记录都会被记录到binlog里,恢复时可以精确到某一行。TRUNCATE是直接把表的数据页整个重置,相当于把一本写满的练习册直接换一本新的,效率极高但无法逐行找回。还有一个关键区别:TRUNCATE会把表的自增计数器重置为1,而DELETE不支持重置(除非你显式把自增值改回去)。也就是说,你清空一张表后,如果希望ID重新从1开始,用TRUNCATE就行;如果还想从上次的ID继续递增,得用DELETE。
TRUNCATE还有一个隐藏特性:它不能在有外键引用的情况下使用。如果其他表的外键指向这张表,TRUNCATE会被拒绝,而DELETE如果删的是子表没引用到的行,倒是可能执行成功。因为InnoDB逐行删除时会检查外键约束,TRUNCATE是按页重置,不检查外键,所以数据库干脆禁止了这种操作。在MySQL 8.0里,TRUNCATE还会隐式提交事务,也就是不支持事务回滚,而DELETE是可以在事务里执行并回滚的。
| 对比项 | DELETE | TRUNCATE |
|---|---|---|
| 是否逐行删除 | 是,逐行匹配并记录日志 | 否,直接重置数据页 |
| 能否加WHERE条件 | 可以 | 不可以,只能全表清空 |
| 事务内是否可回滚 | 可以 | 不可以,隐式提交 |
| 是否重置自增ID | 不重置 | 重置为初始值 |
| 对空间占用影响 | 空间复用但文件不一定变小 | 删除数据页,空间立即标记待用 |
| 外键约束 | 逐行检查 | 不允许操作 |
这个表格建议存一下。选择方案时我一般是:要精准删除部分数据,用DELETE;要清空整张表、且不担心事务回滚的,用TRUNCATE。日常开发里,TRUNCATE用得少,因为大多数表都有保留价值,哪怕是所有数据都过期,也常会用DELETE加条件分批次删,而不是一把梭清空。
4.3 物理删除还是逻辑删除
新人写项目,一接到“删除用户”、“删除订单”这种需求,第一反应就是DELETE。但真实业务系统里,硬删除(物理删除)要非常谨慎。电商平台的订单删除用户后,订单状态变成“已取消”,但这条记录得保留,用于对账、售后、司法取证。用户的账号注销,也不是直接DELETE用户表,通常是把status字段更改为-1,或者打一个deleted_at时间戳。
这种保留数据、只改变标记的做法叫逻辑删除,也叫软删除。它最大的好处是数据可追溯,随时可以“反悔”。当然也有代价:每次查询都要记得过滤掉已经标记删除的记录,否则统计结果会虚高。很多框架比如MyBatis-Plus有逻辑删除插件,配置一个deleted字段后,查询会自动加上WHERE deleted = 0,DML操作也会被自动改写。如果你给业务做设计,我建议默认优先用逻辑删除,只有当数据量实在太大、或者有法律法规要求彻底删除时,再考虑物理删除。划分标准很简单:这条数据删了之后,有没有可能在某一天被查到、被统计到、被恢复?只要能说不上来“不可能”,就说明该用逻辑删除。
5. 事务加持:让增删改更安全
5.1 事务到底是什么
事务把多条DML语句打包成一个原子性的执行单元,要么全成功,要么全失败,不会出现执行了一半的情况。现实比喻就是转账:A账户扣1000、B账户加1000,这两步必须作为一个整体。如果扣款成功、加款失败,整个事务回滚,A账户的1000也恢复原样,不会让钱凭空消失。
MySQL的InnoDB引擎原生支持事务。MyISAM引擎在MySQL 8.0已经被移除了,因为它不支持事务、不支持崩溃恢复,数据安全系数很低。你只要记住:目前的主流默认引擎InnoDB,事务是可靠的。
5.2 手动事务的正确写法
MySQL默认自动提交(autocommit=1),每一条DML语句独立成一个事务。需要多条语句作为一个整体时,要手动控制:
START TRANSACTION; UPDATE account SET balance = balance - 1000 WHERE id = 1; UPDATE account SET balance = balance + 1000 WHERE id = 2; COMMIT;如果第二条UPDATE执行失败,你可以在应用程序里发送ROLLBACK,把第一个UPDATE的结果也撤销掉。判断是否成功,在命令行里可以看ROW COUNT和是否有报错;在Java里,通常是捕获异常后主动rollback。这条顺序能严格控制的情况下,事务的性能影响很小,但能换来数据一致性,这笔账怎么算都划算。
5.3 事务隔离级别:不用背,但要知道有这么回事
事务的核心特性有四个,ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。其中隔离性对新人最不直观。多个事务同时操作同一行数据,如果没有隔离,就会出现脏读、不可重复读、幻读这些问题。MySQL的默认隔离级别是Repeatable Read(可重复读),意思是一个事务里多次查询同一数据,结果是一致的,不会因为其他事务的提交而变来变去。这个默认值已经解决了很多不必要的麻烦,所以新手阶段你不需要调整隔离级别,但至少要知道有这个概念。
隔离级别影响DML的一个重要场景是锁。行级锁在InnoDB里是默认启用的:一个事务UPDATE一行,另一个事务再UPDATE同一行时会被阻塞,等第一个事务提交或回滚后才能继续。这个机制保证了并发写不出错,但也带来了一个问题:长事务会长时间锁住热数据行,导致业务响应变慢。我遇到过一个案例:某个定时任务在一个事务里更新了几千行数据,事务没提交,导致前台用户下订单时同一行数据被锁,一直等待,最终出现大量超时。所以写DML时,事务保持简短,COMMIT要快,锁的持有时间自然也就短了。
6. 实战演示:一个完整的学生管理系统中的增删改操作
6.1 场景设计
为了让你把前面零散的知识串起来,我模拟一个学生管理系统的最核心操作。假设有一张学生表:
CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age TINYINT UNSIGNED, class_id INT, status TINYINT DEFAULT 1 COMMENT '1-在读,0-离校', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );插入两条初始数据:
INSERT INTO student (name, age, class_id) VALUES ('张三', 20, 1); INSERT INTO student (name, age, class_id) VALUES ('李四', 21, 2);6.2 完整操作流程演示
新学年来了,王五入学:
INSERT INTO student (name, age, class_id) VALUES ('王五', 19, 3);张三转班,从1班转到2班:
UPDATE student SET class_id = 2, update_time = NOW() WHERE name = '张三';注意这里用name = '张三'定位是有风险的,因为现实中可能有两个张三。实务开发里,WHERE条件一定要用唯一主键ID,比如WHERE id = 1,否则可能一次更新多条记录。演示代码图方便就用名称了,你到了正式项目千万别这么写。
李四毕业离校,逻辑删除:
UPDATE student SET status = 0 WHERE id = 2;这部分数据其实还留在表里,只是业务查询时过滤掉status=0的用户。需要物理删除一条测试数据时:
DELETE FROM student WHERE id = 3;但这些操作往往混合在一个业务流程里。例如批量导入学生名单:先校验表格数据,再循环执行INSERT,如果某几条数据格式不对,可能导致整个导入失败。正确做法是把整个导入包在一个事务里,任一条失败就整体回滚,避免半截数据留在库里。这种“要么全部成功,要么一条都不放进去”的思维,就是DML操作最重要的素养。
6.3 备份与恢复的简单套路
说到DML操作,就不能不提如何不把数据玩脱。最轻量级的备份方案是mysqldump逻辑备份:
mysqldump -u root -p database_name > backup.sql恢复时执行:
mysql -u root -p database_name < backup.sql还有一个效率更高的工具是MySQL 8.0带来的物理备份工具,但那是进阶话题。新手阶段你有两条底线:第一,重要操作前先备份;第二,在事务里执行重要的DELETE或UPDATE,先START TRANSACTION,确认无误再COMMIT。这样万一出问题,可以在没有关闭会话时用ROLLBACK及时救回来。
7. 常见问题与排查思路:那些年我们踩过的坑
7.1 UPDATE或DELETE执行不下去,一直卡着
最常见的现象是执行一条UPDATE或DELETE时,命令行或客户端一直转圈,没有报错也不返回。这是被锁了。另一个会话正在修改同一行数据,事务还没提交,你的语句在等待行锁。排查方法是查performance_schema里的锁等待,或用SHOW PROCESSLIST看当前有哪些连接在跑什么语句。简单判断:那个lock前面有个连接的状态是“Updating”或者“Sending data”,就说明它占着锁。
遇到锁等待,第一反应不是立刻kill进程,而是要问:占锁的事务是不是一条很长的事务。杀掉占锁会话前要谨慎,如果它的事务正在写重要数据,杀掉可能造成部分回滚,但只要没提交,数据可以回滚到一致状态,不至于脏。生产环境里处理锁等待的规范流程是:先定位阻塞源头,评估是否可以等待,确实需要才KILL掉阻塞会话,然后快速提交你自己的事务,减少锁持有时间。
7.2 忘记WHERE导致数据全没
这一类问题没有任何技术难度,纯粹是习惯问题。DELETE或UPDATE不带WHERE或者WHERE写错,导致全表数据被清空或全部被改错。遇到这种情形,先不要慌,立刻评估有没有备份和binlog。如果误操作前有备份,恢复即可;如果没有,binlog也许能反转。
举一个真实的恢复案例:某天凌晨3点,运营同事把用户表的status字段全部UPDATE成0,本该是3个月前的老用户。因为UPDATE是逐行记录binlog的,我们通过解析binlog,把凌晨3点前那段日志对应的行状态读出来,然后生成一批新的UPDATE语句,把每个用户恢复成操作之前的值。整个过程大概花了一个多小时,最终数据完全恢复。这个案例的关键是:binlog配置的是ROW格式,日志里保留了修改前和修改后的值,才可以精准恢复。如果你配置的是STATEMENT格式,日志里只记录SQL语句本身,恢复还原度就低了。所以我会建议生产环境一律使用ROW格式的binlog,方便排查和误操作恢复。
7.3 插入中文报错或乱码
向表里插入中文出现乱码,或者报错Incorrect string value,一般是字符集没有设置正确。MySQL 8.0默认字符集是utf8mb4,能存emoji和所有中文。如果你的表和库还停留在latin1或utf8mb3,就需要改成utf8mb4。建表时建议直接写明字符集:
CREATE TABLE student ( ... ) DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;utf8mb4_unicode_ci是比较规则,它对大小写不敏感,适合大多数场景。如果你要考虑更精确的排序,可以用utf8mb4_0900_ai_ci(MySQL 8.0默认)。字符集优先在建库建表阶段规划好,不要寄希望於事后转变字符集,那是一场灾难级的工作量。
7.4 NULL值参与运算的坑
DML里NULL是个隐形地雷。例如:
UPDATE student SET score = score + 5 WHERE id = 5;如果原来score = NULL,那么运算结果不是5,而是NULL。因为在SQL中,NULL和任何数字做四则运算,结果都是NULL。这会导致原本没成绩的学生,更新完后成绩还是空值。排查这类问题需要两个意识:一是知道SQL的NULL传播规则;二是提前做空值处理,比如:
UPDATE student SET score = IFNULL(score, 0) + 5 WHERE id = 5;用了IFNULL,当字段为NULL时先替换成0,再做加法,结果就是5了。这个函数在INSERT、UPDATE里都很常用,学DML的时候顺手把它记住,后面写查询也少不了它。
7.5 数值越界或类型不匹配
insert into student (age) values ('二十')会报错。MySQL对类型检查其实不算严格,有些情况它会自动做隐式转换,字符串的数字可以插入到INT列里,但纯中文不行。年龄设计成TINYINT的话,范围是0到255,插一个300进去会报OUT OF RANGE错误。最佳实践是字段类型设计时留足余量,同时代码层面做参数校验,别让错误数据到达数据库层。
8. 几个值得坚持的DML习惯(多年经验总结)
做DML操作这行,纯靠技术挡不住失误,真正能救命的是习惯。我把自己多年攒下的经验整理成几条铁律,你可以直接抄走。
第一,写UPDATE和DELETE之前,先写SELECT确认影响范围。哪怕只多花十秒钟,也能避免“炸掉一张表”的惨剧。第二,重要的写操作都在事务里执行,COMIT确认无误后再提交。这条适用于任何需要两步以上变化的场景。第三,批量写入数据时控制每个批次的行数,不要一条SQL包几千上万条插入,容易让数据库服务压力飙高。第四,历史数据或敏感数据宁可保留,不要随意物理删除。逻辑删除是好习惯,查询时带上状态过滤就行。第五,生产环境一定开启binlog,并配置为ROW格式。做不到这一点,就没有资格操作重要数据。
我见过太多新手对着工具咔咔点,DELETE一把梭,最后数据找不回来,一脸无辜地看着我说“我没想到会这样”。所以我会反复强调:DML操作永远是“谨慎第一,效率第二”。SQL的编写速度可以通过练习提升,但安全意识必须从一开始就建立。一个高级开发者和初级开发者的差别,往往不是谁SQL写得快,而是谁更少把生产库搞出事故。
如果这章的内容你能吃透,并且把上面几条习惯内化成肌肉记忆,那么MySQL的增删改这一关就算真正过了。后面再学存储过程、触发器、分区表这些进阶内容,会更加从容。接下来我还会继续更新查询、索引优化、事务隔离级别的高级用法,跟着这个节奏走,MySQL这条路会越走越稳。