上午后两节课讲MySQL,从数据定义语言(DDL)讲到数据操作语言(DML),最后还扯到了数据控制语言(DCL)。下课的时候,有同学盯着笔记问我:这三类命令到底按什么标准分的?为什么CREATE TABLE有的机器能回滚、有的机器不能回滚?GRANT授权之后为什么还要刷新?这些问题非常典型。今天干脆把这三块内容完整拆一遍,把常见的坑和容易混淆的命令全说清楚,不管是准备考试的计算机基础课学员,还是刚接触数据库的开发者,看完应该都能在自己机器上跑一遍。
1. SQL语言分类的底层逻辑:别只会背名字
1.1 为什么数据库命令要分成这几类
SQL不是一门单纯的计算语言,它朝着"管理数据"这个目标拆成了几个职责完全不同的阵营。DDL(Data Definition Language)管的是"表长什么样",DML(Data Manipulation Language)管的是"表里的数据怎么变",DCL(Data Control Language)管的是"谁能动这些数据"。这三类命令在底层执行逻辑上有非常明显的区别,区分它们的第一个标准是:操作的是结构还是数据。
比如CREATE TABLE、ALTER TABLE这种命令,操作的是数据库的元数据——表结构、字段定义、约束条件,它是"盖房子"。而INSERT、UPDATE、DELETE操作的是表里的实际记录,它是"房子里住的人怎么换"。如果理解不了这一点,就很容易在需要改字段类型的时候去删数据重建表,或者在需要删数据的时候去DROP TABLE把整个表结构都干掉。
第二个区分标准是事务特性。在InnoDB存储引擎下,DML默认是支持事务的,可以COMMIT也可以ROLLBACK;而DDL绝大多数是隐式提交的,执行完直接生效,没有后悔药。平时课堂上很多同学问"为什么我的DELETE可以回滚,DROP却不能回滚",根源就在这个分类逻辑上。DCL也有自己独特的行为,授权和回收权限之后通常需要刷新权限缓存才能让会话感知到变化。
1.2 一张表看懂四大语言阵营
在这里我直接列一张对照表,把常见的SQL命令按阵营分好,便于记忆也便于查阅。除了标题里提到的DDL、DML、DCL之外,我建议顺手加上TCL(事务控制语言)。虽然很多教材不单独提,但实操中COMMIT、ROLLBACK几乎天天用,和DML绑定极深。
| 语言类型 | 英文全称 | 作用对象 | 常见命令 | 是否隐式提交 |
|---|---|---|---|---|
| DDL | Data Definition Language | 数据库、表、索引等结构 | CREATE, ALTER, DROP, TRUNCATE, RENAME | 是 |
| DML | Data Manipulation Language | 表内的数据行 | INSERT, UPDATE, DELETE, SELECT | 默认手动提交(取决于autocommit) |
| DCL | Data Control Language | 用户、权限 | GRANT, REVOKE, CREATE USER, DROP USER | 是 |
| TCL | Transaction Control Language | 事务 | COMMIT, ROLLBACK, SAVEPOINT | 控制事务边界 |
这里尤其要提的是SELECT命令。标准SQL里其实有专门的一类DQL(Data Query Language)来放SELECT,但在实际教学中,SELECT因为和INSERT、UPDATE、DELETE经常一起出现在"增删改查"的场景里,所以普遍划在DML里讲。我不会去纠结这个分类是否绝对规范,重要的是你心里清楚:查询不改变数据,但查询结果是所有操作的基础。
2. DDL数据定义语言实操拆解
2.1 建库建表:字符集与字段类型选择是关键
DDL的第一件大事就是建库建表。很多新手在建表时要么不指定字符集,要么随手用默认类型,后面插入中文就乱码。这里我建议从一开始就养成习惯:数据库和表都显式指定utf8mb4,这样能存中文、特殊符号甚至emoji。注意老版本的utf8是够用的,但utf8mb4才是完整的UTF-8实现,尤其是在MySQL 5.7之后的版本里,utf8mb4已经是标配。
建库和建表的基本语法如下:
-- 建库,指定默认字符集 CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 切库 USE school; -- 建表,指定引擎和自增起始值 CREATE TABLE student ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', stu_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT NOT NULL DEFAULT 1 COMMENT '性别 1男 0女', birthday DATE DEFAULT NULL COMMENT '出生日期', score DECIMAL(5,2) DEFAULT 0.00 COMMENT '综合成绩', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表';字段类型的选择上,我个人给出了几个实用建议:整数优先用INT,超过21亿条数据再考虑BIGINT;短文本用VARCHAR而不是CHAR,VARCHAR会根据内容动态分配空间,CHAR适合长度固定且访问频繁的字段(比如身份证号、学号);金额和成绩用DECIMAL而不是FLOAT或DOUBLE,否则很容易出现精度失真。日期类型里,只存"年月日"用DATE,要存时分秒就用DATETIME,除非明确知道需要跟时区打交道,否则不建议用TIMESTAMP。
COMMENT注释看起来不起眼,但后续维护表结构时省太多事。我有一次接手老项目,一张表十几个字段没有一个注释,全靠猜每个字段的意思,那种痛苦经历过的人都懂。所以,从建表第一天开始就养成写注释的习惯,这是值得长期坚持的。
2.2 ALTER TABLE修改表结构:常用操作全集
ALTER TABLE是DDL里用的最频繁、也最容易操作失误的命令。它主要干五种事:加字段、删字段、改字段类型、改字段名、改表名。每种操作都有对应语法,我分别拆一下。
-- 追加字段 ALTER TABLE student ADD COLUMN phone VARCHAR(20) DEFAULT NULL COMMENT '手机号'; -- 在指定字段之后加字段 ALTER TABLE student ADD COLUMN address VARCHAR(255) DEFAULT NULL COMMENT '住址' AFTER birthday; -- 修改字段类型(注意字段名不变) ALTER TABLE student MODIFY COLUMN phone VARCHAR(15) DEFAULT NULL COMMENT '联系电话'; -- 修改字段名和类型 ALTER TABLE student CHANGE COLUMN phone mobile VARCHAR(20) DEFAULT NULL COMMENT '手机号'; -- 删除字段 ALTER TABLE student DROP COLUMN address; -- 修改表名 ALTER TABLE student RENAME TO student_info; -- 或者 RENAME TABLE student TO student_info;MODIFY和CHANGE的区别是个高频考点。MODIFY只改类型、约束、注释,不改字段名;CHANGE可以同时改字段名,所以CHANGE后面要写两次字段名(旧名和新名)。新手很容易写成ALTER TABLE test CHANGE new_name INT,结果报错,就是因为漏了旧字段名这一步。
在课上反复强调的一点是:ALTER TABLE在大表上执行时可能会长时间锁表。如果一张线上表有几百万行数据,执行ALTER TABLE去修改字段长度,很多时候不能在线完成。MySQL 5.6之后引入了在线DDL(ALGORITHM=INPLACE),但也不是所有操作都能支持。日常开发直接跑问题不大,生产环境改大表结构时,还是要用pt-online-schema-change这类工具,或者在业务低峰期操作。
2.3 DROP、TRUNCATE、DELETE:三个删除命令的分工
这三个命令是DDL和DML交叉区域最大的混淆点。我建议用一句话总结:DROP是把整张表扔掉,TRUNCATE是把表里的数据清空并重置自增计数,DELETE是逐行删除满足条件的数据。三者的区别在课堂上演示非常直观:
| 操作 | 类型 | 是否走事务 | 是否能回滚 | 是否保留表结构 | 自增是否重置 |
|---|---|---|---|---|---|
| DELETE FROM student | DML | 是 | 可以(在事务内) | 保留 | 不重置 |
| TRUNCATE TABLE student | DDL | 否 | 不可以 | 保留 | 重置 |
| DROP TABLE student | DDL | 否 | 不可以 | 删除 | 无 |
重点提醒一下TRUNCATE,很多人以为它和DELETE FROM不加WHERE的效果一样,其实差很远。TRUNCATE在绝大多数MySQL版本下是隐式提交的,一旦执行就结束事务,不能回滚。它还有一个特点就是重置AUTO_INCREMENT计数,下一次插入的主键会重新从1开始。DELETE则不会重置计数,哪怕你把所有数据全部删除,再插入新数据时自增ID也还是接着原来的值走。
我记得有一次课堂上,一个同学在测试环境执行了TRUNCATE TABLE,随后立刻后悔了,跑过来问能不能恢复。我告诉他测试环境还好,生产环境如果没做备份,TRUNCATE之后基本只能靠之前的备份来恢复,binlog虽然能记录,但恢复成本极高。所以这三个删除命令在动手前一定要先想清楚:我到底要删结构、清空数据,还是删部分符合条件的行?
3. DML数据操作语言细节剖析
3.1 INSERT插入数据:单行、多行、批量从他表导入
INSERT在DML里是最温柔的操作,只会增加数据,不会误伤。但依然有很多细节值得注意。最基础的是单行插入与多行插入,实际开发中更推荐多行VALUES一次插入,因为一次IO就能搞定,性能比循环单行INSERT好得多。
-- 单行 INSERT INTO student (stu_no, name, gender, birthday, score) VALUES ('2026001', '张三', 1, '2008-03-15', 88.50); -- 多行批量 INSERT INTO student (stu_no, name, gender, birthday, score) VALUES ('2026002', '李四', 0, '2008-07-26', 91.00), ('2026003', '王五', 1, '2007-11-05', 76.50), ('2026004', '赵六', 0, '2008-01-18', 83.20);这里有两个非常实用的场景。第一个是INSERT IGNORE,它可以在主键或唯一键冲突时跳过插入,不报错。第二个是ON DUPLICATE KEY UPDATE,冲突时改成更新操作。比如在业务里维护一张统计表,每次来一条新数据就想累计某个字段,用这个语法特别顺手。
-- 有学号冲突就忽略,不报错 INSERT IGNORE INTO student (stu_no, name, score) VALUES ('2026001', '张三', 90); -- 有学号冲突就更新分数 INSERT INTO student (stu_no, name, score) VALUES ('2026001', '张三', 95) ON DUPLICATE KEY UPDATE score = VALUES(score);顺便说一下,从另外一张表批量导入数据最常用的写法是INSERT INTO ... SELECT,不需要一条条拼INSERT。这在校验数据、做数据迁移的时候非常有用。
INSERT INTO student_copy (stu_no, name, score) SELECT stu_no, name, score FROM student WHERE score >= 80;实操中最常见的INSERT报错是字段数不匹配、字符串忘了加引号、字段值超过了字段定义的范围。尤其是字符串忘加引号这个,几乎是零基础学员的必踩坑。比如上面VALUES里的张三必须写成'张三',少一层引号MySQL就会当成字段名去解析,直接抛Unknown column错误。
3.2 UPDATE与DELETE:WHERE条件决定生死
UPDATE和DELETE是整个SQL语言中最危险的操作,因为它们都有"不带WHERE就全表遭殃"的属性。我在课堂上做演示时一定会先把WHERE写上,再用SELECT查一遍确认条件范围,最后才执行更新或删除。这个习惯放进生产环境,实在能避免很多事故。
-- 更新前先查一遍,确认影响范围 SELECT * FROM student WHERE stu_no = '2026001'; -- 执行更新 UPDATE student SET score = 95.50 WHERE stu_no = '2026001'; -- 删除前同样先查 SELECT * FROM student WHERE stu_no = '2026004'; -- 执行删除 DELETE FROM student WHERE stu_no = '2026004';UPDATE的底层逻辑是:找到满足WHERE的记录,然后修改对应字段。这里有一个容易忽略的现象:如果你UPDATE的值和原值一模一样,MySQL默认不会修改记录,并且受影响行数会显示为0。这一点不影响正确性,但很多人看到"0 rows affected"就以为自己没更新成功,其实数据本身就是目标值,不用虚惊。
DELETE删除数据后,表文件在磁盘上并不会立刻缩小,只是把记录标记为删除,空间由后续新插入的数据复用。有些人发现删了几万行数据,表空间一点没变,就是这个原因。真要让磁盘空间立刻释放,得用OPTIMIZE TABLE或重建表,日常维护里不是每删一次数据都需要做。
事务内DELETE之后如果发现删错了,只要还没COMMIT,就可以ROLLBACK恢复。这也是DML和DDL最大的区别。下面这个例子非常直观,建议在本地亲手操作一遍:
START TRANSACTION; DELETE FROM student WHERE score < 60; -- 查一下发现误删了,立刻回滚 ROLLBACK;3.3 SELECT查询:不只是SELECT *那么简单
SELECT虽然是查询,但在数据操作里它的地位最高。可以说写SQL的百分之七十时间都花在SELECT身上。基础的正常查询顺序是:FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY -> LIMIT。很多学员刚接触时会把WHERE和HAVING搞混,简单理解就是:WHERE是给原始数据行过滤,HAVING是给分组后的结果过滤。
-- 条件查询 SELECT stu_no, name, score FROM student WHERE score >= 60 ORDER BY score DESC; -- 分页查询:跳过0条取10条 SELECT * FROM student LIMIT 10 OFFSET 0; -- 等价写法 SELECT * FROM student LIMIT 0, 10; -- 模糊查询,注意%的位置 SELECT * FROM student WHERE name LIKE '张%';分页性能的坑必须要讲。LIMIT 100000, 10这种深分页在百万级数据上性能极其糟糕,因为MySQL要先扫出前面十万条再扔掉。数据量大时,可以改成基于主键定位的方式,例如记住上一页最后一条记录的主键,然后WHERE id > 上一页最大id LIMIT 10,效果提升非常明显。
ORDER BY排序时,如果不想自己写字段名,可以用字段序号。SELECT name, score FROM student ORDER BY 2 DESC表示按第二列score排序。这个技巧写复杂查询的时候挺方便,但可读性较差,我建议关键SQL还是老老实实写字段名。
还有一个常被忽略的细节:SELECT *会把所有列都查出来,在开发初期图省事没问题,但生产环境尤其是对接接口时,尽量明确列出需要的字段。这不仅仅是性能问题,更关乎代码可读性和后续维护,数据量大之后多几列不用但传输的字段,差距会非常明显。
4. DCL数据控制语言:权限分配与安全问题
4.1 用户管理与登录权限
DCL的核心是管人、管权限。第一步是用户管理,建议每个应用都单独建账号,别用root去跑业务。MySQL创建用户的语法如下:
-- 创建用户,host指定允许登录的地址 CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'your_password'; -- 允许所有地址登录(不太安全,按需设置) CREATE USER 'app_user'@'%' IDENTIFIED BY 'your_password';这里的host字段是很多人容易忽略的点。'app_user'@'%'表示从任何IP都能连,'app_user'@'localhost'表示只能本机连,'app_user'@'192.168.1.%'表示只能从指定网段连。如果是生产数据库,账号尽量限制到具体IP,可以大幅降低被爆破的风险。
-- 修改密码(5.7之后推荐写法) ALTER USER 'app_user'@'localhost' IDENTIFIED BY 'new_password'; -- 删除用户 DROP USER 'app_user'@'localhost';4.2 GRANT授权:权限粒度决定安全边界
创建完用户以后,还需要授权,不然用户什么都做不了。GRANT的授权粒度可以从全局到库再到表,甚至细化到字段级别。申请权限的时候就按"最小权限原则"来,只用SELECT权限就别顺手给UPDATE,否则一旦应用被注入SQL,损失会被放大很多倍。
-- 只授一个库的所有权限 GRANT SELECT, INSERT, UPDATE, DELETE ON school.* TO 'app_user'@'localhost'; -- 只授一张表 GRANT SELECT ON school.student TO 'app_user'@'localhost'; -- 授所有库所有权限(相当于超级管理员,慎重) GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'localhost'; -- 真正生效 FLUSH PRIVILEGES;权限层级从小到大是:表权限、库权限、全局权限。当多个层级权限同时存在时,MySQL会把它们合并,不是覆盖关系。什么意思呢?比如A用户有全局SELECT权限,又在某张表上被REVOKE了SELECT,那他在那张表上依然不能查,但其他表能查。实际场景里很少需要这么精细的控制,但理解合并逻辑对排查权限问题很有帮助。
有一次我在生产环境排查一个诡异的问题:新创建的用户怎么都连接不上数据库,最后发现CREATE USER确实成功,但忘记GRANT任何权限。MySQL默认新建用户一个权限都没有,连登录校验通过之后,执行任何查询都会报"SELECT command denied"。这不是密码错,而是授权漏了。
4.3 REVOKE回收权限与查看授权
权限回收用REVOKE,语法和GRANT是反着的:
-- 撤销删除权限 REVOKE DELETE ON school.* FROM 'app_user'@'localhost'; -- 撤销所有权限 REVOKE ALL PRIVILEGES ON school.* FROM 'app_user'@'localhost';排查用户到底有什么权限,使用SHOW GRANTS:
SHOW GRANTS FOR 'app_user'@'localhost';实操经验里有个坑:修改了权限表之后,某些已存在的连接不会立刻生效,必须得等会话重建或者在执行完GRANT/REVOKE后执行FLUSH PRIVILEGES。尤其是使用ALTER USER修改密码,已经登录的会话不会马上断开,新密码要等下次连接才生效。上课时有同学问"为什么刚改了密码,同事那边还能继续连",就是因为他那台机器上保留着旧的连接池,重新释放重连才会用新密码。
DCL这块在计算机基础课程里通常不会讲得太深,但我觉得每个开发者都应该掌握最基本的用户和授权操作。因为实际项目中几乎不会让你用root跑业务应用,迟早得自己创建账号、分配权限。把GRANT和REVOKE练熟了,等于给自己的数据库装了一道安全气囊。
5. 事务控制语言TCL:这里藏着"控制"的深层含义
5.1 为什么DML离不开事务
说到数据控制语言,很多人第一反应是DCL的权限控制,但课堂后段我会额外补充事务控制这一层。因为DML操作和事务控制绑定最紧,日常写增删改,如果不懂COMMIT、ROLLBACK,极易造成数据脏乱。事务要解决的核心问题其实可以用一个场景解释:银行转账。
A账户扣1000元,B账户加1000元,两条UPDATE必须打包成一个整体。如果第一条成功、第二条失败,数据库应该回到转账前的状态,谁的钱都不少。这就是事务的原子性。引申出的四种特性合起来就是ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
InnoDB是默认支持事务的存储引擎,MyISAM不支持。如果你发现某个表执行START TRANSACTION和ROLLBACK完全无效,先检查一下存储引擎,大概率是MyISAM。这也是为什么我在建表时总强调ENGINE=InnoDB。
5.2 COMMIT、ROLLBACK、SAVEPOINT的实战用法
实操中事务的基本写法如下:
-- 开启事务 START TRANSACTION; UPDATE student SET score = score - 5 WHERE stu_no = '2026001'; UPDATE student SET score = score + 5 WHERE stu_no = '2026002'; -- 确认无误后提交 COMMIT; -- 如果发现异常,可以回滚 ROLLBACK;SAVEPOINT是事务中间的存档点,适合长事务中分段回滚。比如一个大事务里做了很多操作,只想回滚到中间某一步,而不是全部回滚,就能用SAVEPOINT。
START TRANSACTION; INSERT INTO student (stu_no, name) VALUES ('2026100', '测试'); SAVEPOINT sp1; INSERT INTO student (stu_no, name) VALUES ('2026101', '临时'); -- 出了点问题,回滚到sp1,只撤销后面这次插入 ROLLBACK TO sp1; COMMIT;关于MySQL的自动提交,课堂上被问得也很多。默认情况autocommit=1,你执行一条INSERT或UPDATE后自动生效,不需要手动COMMIT。但当你手动START TRANSACTION之后,autocommit就暂时失效了,必须显式COMMIT或ROLLBACK结束事务。理解这一点对排查"为什么手动开了事务后,别的会话看不到数据变化"很有帮助。
5.3 事务隔离级别与并发问题
既然控制语言谈到了事务,就绕不开隔离级别。MySQL默认的隔离级别是REPEATABLE READ(可重复读),它通过MVCC机制解决了大部分并发问题。四个隔离级别从松到严分别是:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。对应的三个经典并发问题分别是脏读、不可重复读、幻读。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | InnoDB下一般不发生 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
课堂演示时我经常用一个例子解释不可重复读和幻读的区别:事务A查成绩,看到张三80分;事务B这时候把张三改成90分提交;事务A再查一次,如果读到90分,就是不可重复读(同一行数据前后不一致)。幻读则是A查询成绩在60到100分的记录总数,B插入了一条新记录并提交,A再查一次总数变了。隔离级别就是解决这类并发矛盾的手段。
串行化级别最安全,但性能最差,实际生产环境很少用。默认的REPEATABLE READ在绝大多数场景下都够用,这也是为什么我不建议随意修改事务隔离级别的原因。很多初学者听说SERIALIZABLE最安全,就跑去改,结果数据库吞吐量直线下降,得不偿失。合理设置索引、把大事务拆小、控制好锁的粒度,远比调隔离级别更高效。
6. 课堂常见翻车现场与排查速查
6.1 大小写敏感导致的"表不存在"
MySQL在同一套系统下大小写规则还算稳定,但在Windows和Linux上的表现完全不同。Windows系统默认对表名不敏感,Linux则敏感。在Linux上创建了一张student表,写SELECT FROM Student就报Table doesn't exist。这种问题在开发环境Windows上发现不了,一上线到Linux测试环境就暴露。规避方法很简单:库名、表名、字段名全部统一小写下划线风格,不要一会Student一会student。
6.2 中文乱码和字符集不一致
乱码问题的排查顺序是:连接层、库表层、客户端。连接层表现为客户端设置了SET NAMES utf8mb4,但库表字符集是utf8或latin1。库表层表现为建表时忘写CHARSET,沿用默认latin1。客户端表现为终端工具本身编码不对,命令行窗口没切成UTF-8。排查的时候用一个命令SHOW FULL COLUMNS FROM student,可以看到每列的字符集;再用SHOW CREATE TABLE student看整个表的定义,基本能定位。
6.3 GRANT之后权限不生效
很多人执行完GRANT后直接去连数据库,发现新权限没生效。首先要确认当前会话是不是新建的权限受连接池缓存影响;其次执行FLUSH PRIVILEGES刷新权限缓存;最后检查授权对象是否匹配,比如你授权给'app_user'@'localhost',但应用通过IP连接,实际匹配的可能是'app_user'@'%'或'app_user'@'具体IP'的规则,权限自然对不上。这种"用户看着对、权限却完全错位"的问题,用SHOW GRANTS FOR对应host一查就明白了。
6.4 其他几个高频错误
缺少WHERE条件的UPDATE和DELETE是生产环境最惨烈的事故类型,没有之一。课堂上为了加深印象,我专门演示过不带WHERE删除整张表数据的后果,好在测试环境可以ROLLBACK。但在生产环境,一旦走了隐式提交就没救了。所以我在课上一再强调:写完UPDATE或DELETE,先看一眼有没有WHERE,再瞄一眼WHERE里的条件是不是可能匹配到多余数据。
字符串不写引号、日期不按格式写、字段类型不一致时MySQL会尝试隐式转换,这也会导致意想不到的结果。比如WHERE phone = 123456根据索引规则,有时候会放弃索引而全表扫描。外键和约束相关的报错,往下看错误码信息通常写得很清楚,07xx开头的多数是操作违反约束。不要太依赖MySQL的报错提示去猜,而是把表结构约束先梳理一遍,大多数INSERT和UPDATE异常都能迎刃而解。
7. 课后复盘:我建议你亲手做一遍的操作
课堂上的知识点再多,不动手很容易忘。我每次带课都会让学员在本机完成一个训练,几步下来基本能把DDL、DML、DCL串起来:
第一步,创建数据库test,并创建两张表:一张用户表user,一张订单表order,user和order有主外键关联。第二步,用INSERT插入至少3条用户数据和5条订单数据。第三步,用UPDATE把某个用户的名字改掉,再用DELETE删除一条订单记录。第四步,用SELECT查询每个用户的订单总量,按订单数量降序排序。第五步,创建只拥有SELECT、INSERT、UPDATE权限的应用账号,测试用这个账号执行DELETE时能不能成功。第六步,开一个事务,INSERT一条数据后ROLLBACK,确认表里没有这条数据。
我自己这几年带项目的体会是,数据库基础学得扎实不扎实,不是看会不会写CREATE TABLE,而是看遇到"删错了怎么办""权限不够怎么办""数据乱码怎么办"的时候,能不能在最短时间内定位原因并恢复。这三类语言的分工看起来很死板,但理解透了之后,你会发现日常开发中几乎所有数据库交互都有章可循,不再是一遇到问题就百度。
最后分享一个小技巧:无论课上还是实际项目里,每次执行有风险的DML或DDL前,先把当前SQL语句放到事务里跑一遍测试,或者先SELECT COUNT(*)看一眼影响范围,这是个成本极低但收益极高的习惯。这个习惯养成之后,可以替你挡掉大多数后悔都来不及的误操作。