1. 从一句“删库跑路”说起:SQL分类到底在分什么
几年前我带过一个新人,接手公司某个业务系统的数据维护工作。他第一次碰生产库,领导让他“把用户表里的冗余字段清一清”,他想了半天,在命令行里敲了一行DELETE,然后按了回车。好在当时有备份,虚惊一场,但那次之后我才意识到,很多人对SQL的理解停留在“能查能改就行”,完全没有意识到SQL本身是有清晰分类的,不同分类对应不同的权限边界、使用场景和风险等级。
SQL,全称Structured Query Language,结构化查询语言,是关系型数据库的世界通用语言。不管是MySQL、PostgreSQL、SQL Server还是Oracle,核心语法大同小异,差别只在方言层面。SQL指令按功能划分,通常分为四类:DDL、DML、DQL、DCL,再加上一个TCL(事务控制语言),一共五类。这五个分类不是学术上的空架子,而是实际工作中每天都在用的“安全边界”。
这篇文章我就围绕“数据库基础操作指令(SQL分类)”这个主题,把每一类的定义、核心指令、适用场景、注意事项串一遍。适合刚入门的开发同学、运维新人,也适合那些写了好几年代码但始终没系统梳理过SQL分类的同行。内容不绕弯子,直接奔着实操去。
2. 为什么必须先搞懂SQL分类:权限意识比命令本身更重要
2.1 分类的本质是“风险分级”
先看一个最简单的例子。同样是操作一张表,SELECT和DROP的后果天差地别。前者是读数据,后者是连表带结构一起抹掉。如果把所有SQL指令混为一谈,那么你脑子里的“执行一下”就没有风险意识。而SQL分类,本质上就是给指令做风险分级:
- DDL(数据定义语言):操作数据库、表、索引等结构对象,影响的是“骨架”;
- DML(数据操纵语言):操作表里的数据行,影响的是“血肉”;
- DQL(数据查询语言):只读不写,影响为零;
- DCL(数据控制语言):管用户和权限,影响的是“谁能碰数据”;
- TCL(事务控制语言):管提交和回滚,影响的是“操作算不算数”。
搞清楚分类,你在执行指令前就会本能地多问一句:这句SQL是改结构,还是改数据?是查数据,还是要删数据?权限够不够?需不需要先备份?这个习惯一旦养成,很多低级事故都能避免。
2.2 分类也是数据库权限设计的基础
从数据库管理员的视角看,SQL分类直接对应权限设计。比如一个只读账号,我们一般只给SELECT权限,连INSERT和UPDATE都不给;一个应用账号,通常只给DML权限,不给DDL权限;而DDL和DCL权限,基本只留在DBA手里。如果没有分类意识,你很难理解为什么某个账号执行CREATE TABLE时报权限不足,也很难在面试或工作沟通中准确描述问题。
我见过不少同事,报错信息都读不全,把“permission denied”笼统地归结为“数据库坏了”,实际上就是权限模型没理解。SQL分类搞清楚了,这类问题一眼就能定位。
3. DDL:动的是表结构,不是数据
3.1 DDL包含哪些指令
DDL全称Data Definition Language,负责定义和管理数据库对象的“结构”。对象包括库、表、视图、索引、触发器、存储过程等。核心指令有:
- CREATE:创建数据库、表、索引、视图等;
- ALTER:修改已有对象的结构,比如加字段、改字段类型、加索引;
- DROP:删除对象,表、库、视图等一并删除;
- TRUNCATE:清空表中所有数据,但保留表结构;
- RENAME:重命名对象;
- COMMENT:给对象或字段添加注释。
3.2 CREATE与ALTER的实操示例
创建数据库和表是最基础的操作,但我见过太多人把“建表”这件事做得很粗糙。比如字段类型选错、没有主键、没有注释、字符集不统一,后面全是要还的债。
以MySQL为例,一个相对规范的建表语句大致长这样:
CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE shop; CREATE TABLE IF NOT EXISTS user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', username VARCHAR(64) NOT NULL COMMENT '用户名', email VARCHAR(128) DEFAULT NULL COMMENT '邮箱', status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1启用 0禁用', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (id), UNIQUE KEY uk_username (username), KEY idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';这段DDL里有几个细节值得注意:
- utf8mb4而不是utf8,因为utf8在MySQL里最多支持3字节字符,emoji和部分生僻字存不进去;
- DATETIME加DEFAULT CURRENT_TIMESTAMP,让插入时自动带时间;
- ON UPDATE CURRENT_TIMESTAMP,让行记录更新时自动刷新时间;
- 索引不是越多越好,选择性低的字段(比如status只有0和1)其实不太适合建索引,这里只是示例。
再看ALTER,这是线上维护高频指令。加字段、改字段、删字段,语法不难,难在影响面。比如一张百万行数据的表,ALTER加字段在MySQL 5.7及以下版本会锁表,业务会阻塞。到了8.0,很多操作支持Online DDL,但依然要评估执行时间。
-- 给user表增加一个手机号字段 ALTER TABLE user ADD COLUMN phone VARCHAR(20) DEFAULT NULL COMMENT '手机号' AFTER email; -- 修改字段类型 ALTER TABLE user MODIFY COLUMN phone VARCHAR(32) DEFAULT NULL COMMENT '手机号'; -- 删除字段 ALTER TABLE user DROP COLUMN phone;3.3 DROP与TRUNCATE的差别,很多人栽在这里
TRUNCATE和DELETE经常被混为一谈,但从SQL分类上,TRUNCATE属于DDL,DELETE属于DML。这个分类上的区别直接决定了它们的性质:
- TRUNCATE是DDL,执行后不经过事务,无法通过ROLLBACK回滚;
- DELETE是DML,走事务,只要没提交就可以回滚;
- TRUNCATE会重置自增ID,DELETE不会(除非你手动指定);
- TRUNCATE速度远快于DELETE,因为它是直接释放数据页,不逐行记录日志。
所以,如果有“清空一张表”的需求,先问自己:要不要保留自增ID的连续性?要不要支持回滚?如果答案是“要”,那就老老实实用DELETE,或者TRUNCATE之前先备份。我个人的习惯是,凡是TRUNCATE和DROP,执行前强制确认环境名称,绝不直接在生产库上敲。
提示:在MySQL里,TRUNCATE和DROP操作默认隐式提交,一旦执行无法撤销。自治愈类操作前,最好先做一次全量备份,或者至少确保有最近的备份可用。
3.4 DDL执行前的检查清单
这些年我自己总结了一套DDL上线前的检查习惯,不一定适合所有团队,但足够通用:
- 确认环境:是开发、测试还是生产?客户端标题栏、连接串、配置文件都看一遍;
- 确认备份:涉及DROP、TRUNCATE、大表ALTER,是否有可用备份;
- 评估影响:大表加字段、加索引,会不会锁表、锁行,会不会拖垮主库;
- 检查依赖:有没有视图、存储过程、应用代码引用了这个表结构,字段改名要全局搜索;
- 规划窗口:核心业务的DDL尽量放在低峰期执行,不要赶在业务高峰期“顺手改了”。
4. DML:数据行的增删改,操作前先想清楚条件
4.1 DML的核心指令
DML全称Data Manipulation Language,是对表里的数据行做增、删、改操作。核心指令就是INSERT、UPDATE、DELETE。它和DDL最大的区别在于:DML操作的是数据本身,不动表结构;它受事务控制,可以提交也可以回滚。
4.2 INSERT的几种写法
INSERT是最常见的写入指令,但写法上有讲究。标准单行插入:
INSERT INTO user (username, email, status) VALUES ('zhangsan', 'zs@example.com', 1);多行插入:
INSERT INTO user (username, email, status) VALUES ('lisi', 'ls@example.com', 1), ('wangwu', 'ww@example.com', 0);从另一张表批量导入:
INSERT INTO user (username, email, status) SELECT name, email, 1 FROM temp_user WHERE status = 1;这里有几个实操经验:
- 多行插入要控制批次大小,一次插入几百行没问题,一次插入几十万行可能造成binlog暴涨、耗时过长,反而不如分批;
- 大批量导入时,可以考虑先关闭唯一约束检查和外键检查,导入完再开启,能显著提速;
- 如果业务要求“记录存在就更新、不存在就插入”,MySQL里有ON DUPLICATE KEY UPDATE,PostgreSQL里有ON CONFLICT,但要注意这类语法对唯一索引有依赖。
4.3 UPDATE:少用无条件的UPDATE
UPDATE的典型风险是无条件更新。我见过一个真实案例:某同事在测试环境写了一条UPDATE,忘了加WHERE,把整个表的某个字段全部更新成了一样的值。虽然发生在测试库,但也侧面说明这个习惯有多危险。
-- 危险示例:全表更新 UPDATE user SET status = 0; -- 规范示例:按条件更新 UPDATE user SET status = 0 WHERE id = 1024; -- 更新前先SELECT确认影响范围 SELECT id, status FROM user WHERE id = 1024;我自己有个习惯:写UPDATE之前,先把WHERE条件复制出来,单独跑一遍SELECT,看影响行数是否合理。确认无误后再把SELECT换成UPDATE执行。这个过程确实会多花十几秒,但能挡住大多数低级事故。
4.4 DELETE:物理删除前,留好退路
DELETE和UPDATE同理,核心风险也是WHERE条件。很多人写DELETE时,脑子里想的是“删掉这条测试数据”,手上敲的却是:
DELETE FROM user WHERE status = 0;如果这张表里status=0的数据有几十万行,那这把操作就直接翻车了。一个更稳妥的做法是:
-- 第一步:先查影响范围 SELECT COUNT(*) FROM user WHERE status = 0; -- 第二步:确认后再删除 DELETE FROM user WHERE status = 0;另外,如果数据量大,DELETE也会很慢,并且会随着删除过程放大锁的范围。对超大表的清理,建议分批删除:
DELETE FROM user WHERE status = 0 LIMIT 1000;重复执行,直到影响行数为0。这种方式能减少单次锁持有时间,避免主从延迟飙升。
4.5 关于“软删除”的设计建议
在实际项目里,我越来越推荐用“软删除”代替物理DELETE:给表增加一个deleted字段,删除操作变成UPDATE:
UPDATE user SET deleted = 1, updated_at = NOW() WHERE id = 1024;查询时统一过滤:
SELECT * FROM user WHERE deleted = 0;这样做的好处显而易见:数据可恢复、审计方便、业务逻辑更安全。代价是查询时多一些过滤条件,索引设计需要把deleted字段考虑进去。对于中小型系统,这个取舍非常划算。
5. DQL:SQL里最常用、也最被低估的分类
5.1 DQL就是SELECT,但SELECT不简单
DQL全称Data Query Language,核心就是SELECT查询。虽然每个写代码的人天天在用,但能把SELECT写利索的并不多。很多人遇到复杂查询就“SELECT * + 全表扫描 + 内存里过滤”,这在数据量小的时候没事,一旦数据量上来,性能问题立刻暴露。
一条SQL语句的执行顺序和书写顺序是不同的。开发者看到的是:
SELECT username, email FROM user WHERE status = 1 GROUP BY email HAVING COUNT(*) > 1 ORDER BY created_at DESC LIMIT 10;但数据库引擎实际按这个顺序执行:
- FROM:确定数据来源;
- WHERE:过滤行;
- GROUP BY:分组;
- HAVING:过滤分组;
- SELECT:确定输出列;
- ORDER BY:排序;
- LIMIT:分页限制。
理解这个执行顺序,你才能回答很多性能问题。比如:WHERE里能不能用SELECT别名?不能,因为SELECT在WHERE之后才执行。HAVING和WHERE的区别是什么?一个过滤分组,一个过滤行。这类问题,SQL分类和查询逻辑搞明白了,自然就有了答案。
5.2 多表关联:JOIN的选择与陷阱
多表查询是DQL的核心难点,同时也是最容易写出隐性问题的地方。常见JOIN类型有INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN,实际项目中用得最多的是INNER JOIN和LEFT JOIN。
举个例子,用户表和订单表:
SELECT u.username, o.order_no, o.amount FROM user u INNER JOIN order o ON u.id = o.user_id WHERE u.status = 1;这个INNER JOIN只返回有订单的用户。如果改成LEFT JOIN:
SELECT u.username, o.order_no, o.amount FROM user u LEFT JOIN order o ON u.id = o.user_id WHERE u.status = 1;那么没有订单的用户也会出现,order_no和amount为NULL。听起来很简单,但实际中翻车最多的情况是:用了LEFT JOIN,却把右表的过滤条件放进了WHERE。这样一旦右表条件过滤掉NULL行,LEFT JOIN就退化成INNER JOIN了。
-- 错误示范:左连接语义被破坏 SELECT u.username, o.order_no FROM user u LEFT JOIN order o ON u.id = o.user_id WHERE o.amount > 100; -- 正确示范:把右表条件放到ON里 SELECT u.username, o.order_no FROM user u LEFT JOIN order o ON u.id = o.user_id AND o.amount > 100;这个细节非常经典。很多线上数据对不上,排查到最后都是这个原因。
5.3 子查询、聚合与分组
子查询分相关子查询和非相关子查询。相关子查询依赖外层查询的值,性能通常较差;非相关子查询独立执行,性能相对可控。能用JOIN解决的问题,尽量不用相关子查询。原因很简单,相关子查询相当于外层每一行都要执行一次内层查询,数据量大时代价感人。
聚合函数GROUP BY是另一个高频点。COUNT、SUM、AVG、MAX、MIN都有各自的语义。COUNT()和COUNT(字段)的差别也值得留意:COUNT()统计所有行,COUNT(字段)只统计该字段非NULL的行。统计口径错了,报表数据就错了,而且这种错误非常隐蔽。
5.4 分页查询与索引的关系
分页是DQL最日常的需求,但很多人写分页不关心性能。一张表有100万条数据,你要看第10万页,如果直接写:
SELECT * FROM user ORDER BY id LIMIT 100000, 20;MySQL会扫描前100020条数据,再丢掉前100000条,只返回20条。这个偏移量越大,查询越慢。更优的写法是利用覆盖索引和延迟关联:
SELECT u.* FROM user u INNER JOIN ( SELECT id FROM user ORDER BY id LIMIT 100000, 20 ) t ON u.id = t.id;子查询里只查主键,走索引很快,然后再回表取完整数据。这个优化思路很多场景下效果非常明显。
提示:DQL虽然是只读操作,但查询本身也有代价。一条不走索引的全表扫描,在高并发下能把数据库CPU打满。所以“只读”不等于“没有风险”,上线前还是要看执行计划。
6. DCL:权限控制,数据库安全的最后一道闸门
6.1 DCL的核心指令
DCL全称Data Control Language,负责权限授予和回收,核心指令是GRANT和REVOKE。MySQL用户管理的基础操作也归在这类里,比如CREATE USER、ALTER USER、DROP USER,虽然有人把用户管理的语句单独归类,但通常讲SQL分类时,DCL指的就是权限控制这一组。
6.2 最小权限原则的落地方式
最小权限原则听起来很抽象,落到具体操作上就是一句话:能读的不给写,能写的不给删,能操作一张表的不给整个库。
创建一个只读账号:
CREATE USER 'readonly_user'@'10.0.0.%' IDENTIFIED BY 'StrongP@ss123'; GRANT SELECT ON shop.* TO 'readonly_user'@'10.0.0.%';创建一个应用账号,只给指定表的增删改查权限:
CREATE USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'AppUser@2024'; GRANT SELECT, INSERT, UPDATE, DELETE ON shop.user TO 'app_user'@'192.168.1.%';回收权限:
REVOKE DELETE ON shop.user FROM 'app_user'@'192.168.1.%';查看某个用户的权限:
SHOW GRANTS FOR 'app_user'@'192.168.1.%';6.3 实际工作中DCL的三个高频场景
第一个场景是交接。员工离职或转岗,账号权限没有及时回收,这是一个很大的安全隐患。我经历过一次内部审计,发现三年前的离职员工账号还能登录测试库,说明权限生命周期管理压根没做。
第二个场景是临时授权。有时候需要让某个人临时处理一批数据,授一段时间权限,用完必须立即回收。很多团队嫌麻烦,临时权限变成永久权限,隐患就这么积累下来的。
第三个场景是区分账号。不要所有人都用同一个root或管理员账号连数据库,至少要区分应用账号、开发账号、运维账号,这样出问题的时候才能定位到人。如果大家都用同一个账号,审计日志等于白写。
6.4 一个容易被忽略的细节:刷新权限
在MySQL里,用GRANT、REVOKE语句操作权限时,一般不需要手动刷新权限,系统会自动生效。但如果你直接操作了mysql.user系统表,那么需要执行:
FLUSH PRIVILEGES;这个区别很多人不清楚。官方推荐的做法是不要直接改系统表,尽量用GRANT和REVOKE,所以我个人基本上没有用过FLUSH PRIVILEGES。另外,授权时一定要限定host,不要写'%',除非你确实需要任意主机都能访问。把host写死,账号安全性会好很多。
7. TCL:事务控制,提交与回滚之间藏着大学问
7.1 TCL的核心指令
TCL全称Transaction Control Language,核心指令是COMMIT、ROLLBACK、SAVEPOINT。它管理的是事务的边界:什么时候开始,什么时候提交,什么时候回滚。
严格来说,MySQL默认是自动提交模式,每条DML语句执行完就自动提交。所以很多人写了半天SQL,都没有“事务”的概念,直到某天需要“多条语句要么全部成功,要么全部失败”,才想起来还有事务这回事。
7.2 一个标准事务的写法
以转账为例,假设从A账户扣1000元,往B账户加1000元,这两步必须原子执行:
START TRANSACTION; UPDATE account SET balance = balance - 1000 WHERE account_id = 'A'; UPDATE account SET balance = balance + 1000 WHERE account_id = 'B'; COMMIT;如果第二步执行失败,可以回滚:
START TRANSACTION; UPDATE account SET balance = balance - 1000 WHERE account_id = 'A'; -- 模拟异常:某一步失败 UPDATE account SET balance = balance + 1000 WHERE account_id = 'B'; -- 发现有问题 ROLLBACK;7.3 隐式提交的坑
不是所有语句都能在事务里回滚。DDL语句、部分管理语句,在MySQL里会隐式提交当前事务,也就是说,你的事务里先执行了DDL,事务就断了。这是一个非常容易踩的坑。
同样,TRUNCATE虽然不包含WHERE条件,也不逐行删除,但它是DDL,执行的时候会隐式提交。事务里执行TRUNCATE,一旦执行,无法用ROLLBACK撤销。这是它和DELETE最本质的区别之一。
7.4 SAVEPOINT的使用场景
SAVEPOINT是事务中的一个“存档点”。假设一个事务里有三步操作,你只想回滚到第二步之前,而不是整个事务全部回滚:
START TRANSACTION; INSERT INTO user (username) VALUES ('a'); SAVEPOINT sp1; INSERT INTO user (username) VALUES ('b'); SAVEPOINT sp2; INSERT INTO user (username) VALUES ('c'); -- 只回滚到sp2,保留前两步 ROLLBACK TO SAVEPOINT sp2; COMMIT;这种写法在实际项目里用得不算多,但处理复杂批处理任务时能派上用场。不过也要提醒一句:SAVEPOINT用得越多,事务越复杂,排查问题越难。简单的场景,少用甚至不用更好。
7.5 事务隔离级别:TCL背后的关键参数
提到事务,就绕不开隔离级别。MySQL默认是REPEATABLE READ,PostgreSQL默认是READ COMMITTED。不同隔离级别下,事务之间的可见性完全不一样。很多人说“我开了事务,为什么另一个会话还能看到未提交的数据”,大概率就是隔离级别理解不到位。
SQL标准定义了四种隔离级别:
- READ UNCOMMITTED:能读到未提交数据,脏读风险高,基本不用;
- READ COMMITTED:只能读到已提交数据,解决了脏读,但可能存在不可重复读;
- REPEATABLE READ:事务内多次读取结果一致,解决不可重复读,但可能存在幻读;
- SERIALIZABLE:完全串行化,性能最低,用于极端一致性场景。
关于隔离级别的完整原理和实现细节,这篇文章不展开,但想强调一点:事务控制不只是COMMIT和ROLLBACK两个指令,隔离级别、锁机制、MVCC,这些都是TCL主题下不可分割的内容。理解了这一层,你对SQL分类的理解才算真正完整。
8. 常见问题与排查技巧实录
8.1 忘记WHERE条件,UPDATE把整表数据改坏了
这是DML类事故中最经典的场景。某天下午,线上数据突然大面积异常,排查后发现是一条UPDATE没有WHERE条件,把全表字段更新成同一个值。说实话,这种错误几乎每个团队都发生过。
排查步骤:
- 先看binlog或审计日志,找到执行这条SQL的会话和来源IP;
- 确认影响范围,定位这张表的数据最后正确的时间点;
- 找到最近的全量备份或binlog记录,做数据恢复;
- 事后复盘,为什么这条SQL被执行,权限控制是否有问题,是否需要限制UPDATE和DELETE的安全模式。
MySQL里有个安全选项可以降低风险:--safe-updates。开启后,没有WHERE条件或没有LIMIT的UPDATE和DELETE会被拒绝执行。对于开发环境和测试环境,强烈建议开启。线上环境如果条件允许,也可以考虑通过代理层或SQL审计工具做类似的防呆控制。
8.2 数据库磁盘被一个误操作塞满了
另一个常见事故是误执行了大范围DELETE,或者没有控制DELETE的批次大小,导致undo日志暴涨、磁盘瞬间写满。表面看是“磁盘空间不足”,实际上是一条SQL引发的连锁反应。
这类问题的处理思路是:
- 先定位正在执行的大事务或长事务,用
SHOW PROCESSLIST看当前会话状态; - 如果事务已经跑了很久,评估是否等待其完成,或者是否手动kill会话;
- 磁盘释放需要时间,不要反复重试同一句SQL;
- 最后检查主从同步延迟,如果从库延迟严重,需要等延迟追平再恢复业务。
8.3 权限不足,报错一堆,不知道从哪排查
新环境里,开发同学经常跑到我这里问,说“连数据库都能连上,但一执行SQL就报权限错误”。这种问题的排查思路很简单:
- 确认当前登录用户:
SELECT CURRENT_USER(); - 查看该用户权限:
SHOW GRANTS FOR CURRENT_USER(); - 确认目标库、目标表的权限是否匹配;
- 确认host是否匹配,很多时候换个网络IP就报权限错误,就是因为grant里限定了host。
权限问题说穿了还是DCL分类没吃透。你把用户、权限、host三个维度对清楚,基本不会有大问题。
8.4 常见问题速查表
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
| UPDATE执行特别慢 | 无索引或索引失效、锁等待 | 查看执行计划,检查锁等待状态 |
| DELETE后空间没释放 | 删除是DML,只是标记数据,空间由后续操作回收 | 定期维护或用TRUNCATE释放空间,注意备份 |
| 事务里执行DDL后无法回滚 | DDL隐式提交事务 | 避免事务中混用DDL和DML |
| 用户能登录但无法建表 | 没有CREATE权限 | 用SHOW GRANTS查看权限 |
| 查询结果和预期不一致 | JOIN条件或过滤条件放错位置 | 逐步简化SQL,验证每一步结果 |
8.5 我自己的一线避坑经验
最后分享几条我踩过坑之后养成的习惯。第一,执行任何有风险SQL之前,先打开一个事务,执行完查一遍结果,确认没问题再COMMIT,发现异常立刻ROLLBACK。这个习惯在开发环境尤其好用。第二,凡是涉及DELETE和UPDATE的操作,写完之后停两秒,问自己一句:WHERE条件是不是我想要的?表名对不对?环境对不对?两秒钟,能挡掉很多灾难。第三,尽量少用SELECT *,不仅浪费网络带宽,而且在表结构变更后容易出现隐性问题。写清楚需要的字段,是对自己和下游同事的尊重。第四,多看看官方文档和慢查询日志,很多SQL问题的答案其实都在执行计划里,只是很多人没有看执行计划的习惯。
9. 后续还可以怎么深入学
SQL分类是数据库学习的骨架,但并不代表全部。如果你把分类搞清楚了,接下来可以沿着几个方向继续深入:索引设计与优化、执行计划分析、事务隔离级别与MVCC原理、数据库备份与恢复策略、容量规划与性能监控。每个方向都能写出一长篇实操笔记,而且每一条都和今天聊的SQL分类有关联。
举个例子,你理解了DDL和DML的区别,就更容易理解为什么备份策略里既有结构备份也有数据备份;你理解了DCL的权限模型,就更容易理解为什么安全审计要追踪到具体账号而不是笼统的“数据库管理员”。数据库这个领域就是这样,基础概念越扎实,上层问题越容易拆解。
我在实际带人的时候,最看重的不是谁记住了多少条命令,而是谁在面对一条SQL时能快速判断它属于哪个分类、有什么风险、影响范围是什么。这个判断能力,比背命令重要一百倍。今天这篇文章算是把SQL分类这条主线理了一遍,给新同学一个抓手,也给老同学一次查漏补缺的机会。希望你在下一次敲下回车之前,能多问一句:这条SQL,我搞清楚它是什么了吗?