简介:数据库系统概论第4章课件,围绕关系数据库标准语言SQL展开,适合高校数据库课程学习者、备考者及需要系统梳理SQL基础的开发人员。内容覆盖SQL概述、数据定义、数据查询、数据操纵与视图等核心章节,通过建表语句、数据类型及完整性约束的实例讲解,帮助读者理解SQL语言一体化和面向集合的操作特点。压缩包内包含1个PPT演示文稿,大小约428KB,页面结构清晰,便于按章节顺序学习或复习。已有100人下载学习,适合作为课堂配套资料或自学入门材料。课件重点展示了CREATE TABLE语法、常用数据类型(数值型、字符型、位串型、时间型、布尔型)及主键、外键、非空、唯一、检查等约束条件,并配有职工表emp和部门表dept的具体建表示例,有助于快速掌握SQL数据定义部分的重点与难点,夯实后续查询、操纵和视图学习的基础。
1. SQL不是查询语句:关系数据库的“通用话术”系统
很多人第一次翻开《数据库系统概论》第4章,看到「关系数据库标准语言——SQL」这个标题,下意识以为整章就是教怎么写SELECT查询。等实际敲命令才发现,SQL是关系数据库的完整话术系统:建库、建表、约束、增删改查、权限管理,全在一门语言里解决。教材习惯把第4章拆成「SQL语言1」和「SQL语言2」两块,第一部分覆盖SQL概述、数据定义、数据更新和基础查询,多表连接与视图留给下一块。这一章最反直觉的地方在于:一张设计糟糕的表,后面SQL写得再漂亮也白搭,而设计表的起点恰恰是SQL语言。下面的内容适合正在啃教材、想用手敲的方式把理论钉死的学生,也适合工作里天天写SQL、但没系统回炉过一遍的从业者。
2. 关系数据库为什么认SQL:从表、行、列到主键外键的底层约定
2.1 关系模型的三个核心概念:关系、元组、属性
关系数据库把世界抽象成二维表。一张表就是一个关系,一行数据叫元组,一列叫属性。教材里写「关系Student(Sno, Sname, Ssex, Sage, Sdept)」不是摆学术架子,而是告诉你这张表在关系模型里的真实身份:它既有集合的严谨,又有表格的直观。
为什么非要把表叫成关系?因为关系模型要求每个属性不可再分、同一属性的所有取值必须同域。比如Sage列只能是数值,不能这一行是18、下一行是「未知」。这种严谨建立在集合论和谓词逻辑之上。SQL能成为关系数据库标准语言,恰恰是因为它把数学基础做成了好用的操作界面:选择、投影、连接这些关系代数的核心运算,都能在SQL里找到对应的子句。换句话说,SQL不是一堆零散语句,而是关系代数在工程上的落地形态。
这个角度能解释很多「为什么」。比如关系模型里表的行是集合元素,理论上不允许重复,但SELECT查询结果却可能因为多表连接而出现重复行,所以才需要DISTINCT做去重。再比如早期的层次模型和网状模型要用指针和路径导航数据,查询路径和应用程序耦合太重,换一个查询方式就要动程序;关系模型改变了玩法:你只描述「要什么」,不描述「怎么找」,SQL负责把「要什么」翻译成执行计划。标准从1986年发布一直演进到现在,MySQL、SQL Server、Oracle这些主流数据库的基本语句都向它靠拢,学通一门,换库只是适应方言的差别。
2.2 主键与外键:表之间的“血缘关系”怎么落地
关系数据库里表不是孤岛。表和表之间靠主键与外键搭关系:主键是唯一标识一行数据的列或列组合;外键是子表里存放的父表主键值。这个「复制过来的引用」就是关系数据库表与表之间关系的本质,它比ER图上的连线更硬核——关系是约束,不是示意图。
学生选课是教材里最经典的例子,三张表就把两个实体和一个多对多关系说清楚了:
CREATE TABLE Student ( Sno CHAR(9) PRIMARY KEY, Sname VARCHAR(20) NOT NULL, Ssex CHAR(2), Sage SMALLINT, Sdept VARCHAR(20) ); CREATE TABLE Course ( Cno CHAR(4) PRIMARY KEY, Cname VARCHAR(40) NOT NULL, Cpno CHAR(4), Credit SMALLINT ); CREATE TABLE SC ( Sno CHAR(9), Cno CHAR(4), Grade DECIMAL(5,2), PRIMARY KEY (Sno, Cno), FOREIGN KEY (Sno) REFERENCES Student(Sno), FOREIGN KEY (Cno) REFERENCES Course(Cno) );Student表里Sno用CHAR(9),学号长度固定,定长类型做主键检索效率更高;Sname用VARCHAR(20)表示最长20个字符、按实际内容存储。Course表里Cpno是先修课课程号,指向自己这张表的主键Cno,这叫自引用外键,用来表达「课程之间有先修关系」。SC表是学生与课程之间的关联表,主键(Sno, Cno)是联合主键,防止同一学生重复选同一门课;两个外键把SC和两张父表绑死,插入SC时学号不在Student里会被拒绝,课程号不在Course里也会被拒绝。
这里有个容易踩的细节:外键列的数据类型必须与父表主键完全一致,CHAR(9)对CHAR(9)可以,CHAR(9)对VARCHAR(9)就不行。学生和课程是典型的多对多关系,靠SC这张中间表拆成两个一对多,这是关系数据库设计里最高频的套路。
提示:建外键前先想清楚子表这列的删除规则。MySQL默认是RESTRICT,也就是被引用的父表行不允许删除,第5章会专门讲这个翻车现场。
2.3 SQL三军:DDL、DML、DCL 各管哪一头
SQL语言按作用分成三块:DDL管结构,DML管数据,DCL管权限。你先有结构,再操作数据,最后给不同账号开不同权限。这层划分在排查语法报错时特别有用:看到syntax error,先判断动词是不是用错了阵营。
| 分类 | 典型动词 | 干什么 | 对应的关系代数 |
|---|---|---|---|
| DDL | CREATE / ALTER / DROP | 定义库、表、索引的结构 | 无直接对应 |
| DML | INSERT / UPDATE / DELETE / SELECT | 操作表里的数据 | 选择对应WHERE,投影对应SELECT列,连接对应JOIN |
| DCL | GRANT / REVOKE | 控制访问权限 | 无直接对应 |
DCL在入门阶段看懂就够了,最少要会读这一句:
GRANT SELECT ON school.Student TO 'readonly_user'; REVOKE INSERT ON school.Student FROM 'readonly_user';这是「谁能看这张表、谁能改这张表」的最小语句。常见做法是给应用账号最小权限,只授它真正要用的操作。很多学生做课程设计时一个root账号用到毕业,这在生产环境就是事故,但第4章阶段先会读会写GRANT,知道有这层控制即可。
SQL里还有事务相关的COMMIT和ROLLBACK,完整性相关的断言和触发器,这些在教材后续章节才展开。SQL语言1的边界通常止步于定义、更新、基础查询,先把这三块练到条件反射,再往上盖后面的楼层。
3. 用SQL建库建表与增删改:手写DDL和DML的最小可复现步骤
3.1 建库建表:从 CREATE DATABASE 到 CREATE TABLE 的完整命令
我一般用MySQL 8.0做演示,你手里是SQL Server或者Oracle也问题不大:SQL Server里没有utf8mb4概念,用NVARCHAR;Oracle的自增列要看版本。但下面这套建库建表语句的骨架,各家通用。第一步建库:
CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4; USE school;为什么指定utf8mb4?MySQL的utf8字符集最多存3字节,存不了emoji和生僻字,utf8mb4才是完整Unicode。MySQL 5.7及以下版本默认不是utf8mb4,不指定的话,中文数据一旦涉及表情符号就乱码,而且这口锅会一直背到换库。8.0版本默认已经是utf8mb4,但显式写出来是给自己的环境加一道保险。
然后建学生表:
CREATE TABLE Student ( Sno CHAR(9) PRIMARY KEY, Sname VARCHAR(20) NOT NULL, Ssex CHAR(2), Sage SMALLINT, Sdept VARCHAR(20) );列参数逐个说:Sno取CHAR(9),学号长度固定,定长类型检索时不需要解析长度前缀,比VARCHAR更适合当主键;Sname用VARCHAR(20),20是最大字符数,实际存多少占多少;Sage用SMALLINT,占2字节,最大能到32767,放年龄绰绰有余。PRIMARY KEY写在列后面是列级约束,如果主键是多列组合,就必须写到表级。
建表后要改结构,用ALTER TABLE:
-- 加一列 ALTER TABLE Student ADD Sbirth DATE; -- 删一列 ALTER TABLE Student DROP COLUMN Sbirth;ALTER TABLE是DDL,执行时会拿表的元数据锁。生产环境的大表加列不要这么直接跑,MySQL 8.0虽然支持INSTANT算法,但也要先看行数和在线DDL的锁策略;课程设计和本地练习随便跑,但习惯要从一开始养好。
3.2 插入、修改、删除:DML三类操作和它们的WHERE陷阱
插入数据用INSERT,常见写法有两类:
-- 写法一:指定列名,没写的列用默认值或NULL INSERT INTO Student (Sno, Sname, Ssex, Sage, Sdept) VALUES ('2024001', '张同学', '男', 20, 'CS'); -- 写法二:省略列名,值必须和建表列顺序一致且全部给出 INSERT INTO Student VALUES ('2024002', '李同学', '女', 19, 'IS');第一种写法任何时候都安全,列名和值一一对应;第二种少打字,但表结构一调整就崩。我习惯在代码里永远写列名,尤其是表列数超过5列时,省得数错位置。
更新数据用UPDATE,这是SQL里最需要敬畏的动词:
-- 给C001课程所有成绩加5分 UPDATE SC SET Grade = Grade + 5 WHERE Cno = 'C001'; -- 危险写法:忘写WHERE,全表成绩一起加5分 -- UPDATE SC SET Grade = Grade + 5;不带WHERE的UPDATE作用于全表。MySQL命令行默认开着autocommit,一条UPDATE下去想反悔只能靠备份,没有后悔药。执行UPDATE前,我一般先把WHERE条件原样搬进SELECT看命中行数:
SELECT COUNT(*) FROM SC WHERE Cno = 'C001';行数对得上,再执行UPDATE。
删除要分清三个层次:
-- 删除具体行 DELETE FROM SC WHERE Sno = '2024001'; -- 清空表但保留表结构 TRUNCATE TABLE SC; -- 删除整张表 DROP TABLE SC;DELETE是DML,逐行删、可带WHERE、可回滚;TRUNCATE是DDL,直接释放存储页,快但不可精确删;DROP是DDL,连表带结构一起消失。课程设计里最怕看到有人用DROP干DELETE的活,一个回车下去,几个月的数据和表结构一起没了。
3.3 约束在INSERT时如何发力:主键冲突与外键失败
约束不是摆设,它在写操作发生的那一刻就会被检查。最常见的两个报错现场:
-- 主键冲突:学号2024001已经存在 INSERT INTO Student (Sno, Sname, Ssex, Sage, Sdept) VALUES ('2024001', '王同学', '男', 21, 'MA'); -- 外键失败:SC引用的学号在Student表里不存在 INSERT INTO SC (Sno, Cno, Grade) VALUES ('2099999', 'C001', 90);第一条会报「Duplicate entry '2024001' for key 'Student.PRIMARY'」;第二条在MySQL下报「Cannot add or update a child row: a foreign key constraint fails」,SQL Server里措辞不同,但指向同一个问题:子表里的引用值在父表主键里找不到。看到这类报错不要先怀疑数据库坏了,去父表查这个值存不存在。
NOT NULL约束也会在INSERT时发力:Student表的Sname建表时写了NOT NULL,插入时不给它,MySQL会报「Column 'Sname' cannot be null」。这个报错好理解,但它提醒一件重要的事:设计表时把NOT NULL用在哪里,直接影响数据质量。比如成绩Grade允许NULL表示缺考或缓考,这个NULL是有业务含义的,所以建表时不该给它加NOT NULL。约束不是越严越好,是要贴合业务语义。
提示:SQL标准把约束分成列级和表级两种写法。单列约束写在列后,联合主键和复合外键必须写在表级,也就是所有列定义完之后。
4. 数据查询是SQL的重头戏:SELECT的过滤、聚合与多表连接的完整拆解
4.1 基本查询与WHERE过滤:等于、模糊匹配和空值三件套
SELECT是所有SQL语句里最复杂的一条,先记住这句口诀:SELECT决定列,WHERE决定行,GROUP BY决定分组,ORDER BY决定顺序。一条SELECT先执行FROM定位表,再执行WHERE筛行,最后SELECT投影列。
-- 查CS系并且年龄小于20的学生 SELECT Sno, Sname, Sage FROM Student WHERE Sdept = 'CS' AND Sage < 20;WHERE里AND的优先级比OR高。混用AND和OR不加括号,是初学者最容易错的地方:WHERE Sdept = 'CS' OR Sdept = 'IS' AND Sage < 20会被解析成「CS系全部 OR(IS系且小于20)」,CS系的老学生也入选了。想表达「CS或IS系且年龄小于20」,必须写成:
SELECT Sno, Sname, Sage FROM Student WHERE (Sdept = 'CS' OR Sdept = 'IS') AND Sage < 20;模糊匹配用LIKE。%代表任意多个字符,_代表任意一个字符:
-- 所有姓张的学生 SELECT Sname FROM Student WHERE Sname LIKE '张%'; -- 名字只有两个字的张姓学生 SELECT Sname FROM Student WHERE Sname LIKE '张_';LIKE在数据量大的表上经常走不了索引,前导模糊匹配尤其如此。学的时候随便用,生产环境上万行的表还写LIKE '%关键词%',就要有做慢SQL优化的心理准备了。
空值是SQL里最坑人的三值逻辑。判断一列是空,不能用= NULL,要用IS NULL:
-- 查缺考或缓考的学生 SELECT * FROM SC WHERE Grade IS NULL;为什么Grade = NULL不行?因为NULL代表未知,任何和NULL做等值比较的结果都是unknown,WHERE只保留true,这些行直接就被丢掉了。想去掉空值,规范写法是IS NOT NULL:
SELECT * FROM SC WHERE Grade IS NOT NULL;SQL语句去重也有固定套路。表是集合,理论上没有重复行,但SELECT结果可能来自多表连接,重复不可避免:
SELECT DISTINCT Sdept FROM Student;DISTINCT对整行去重,不是对某一列单独去重。想按Sdept去重、同时拿到每一组的代表值,那是GROUP BY的领域。
4.2 聚合与分组:GROUP BY 与 HAVING 的分工
统计是SQL最高频的场景。五个聚合函数记牢:COUNT统计行数,SUM求和,AVG求平均,MAX和MIN取最值。COUNT有一个容易被忽视的行为:
-- 统计SC表总行数 SELECT COUNT(*) FROM SC; -- 统计有成绩的行数,NULL会被跳过 SELECT COUNT(Grade) FROM SC;COUNT(*)数行,COUNT(Grade)数Grade非空的行。统计「有效成绩个数」必须用COUNT(Grade),直接数行会把缺考的也排进去。这是用SQL清洗数据时最常见的差异,也是面试里爱挖的坑。
分组聚合解决「每个」类问题。查每个学生选了几门课:
SELECT Sno, COUNT(*) AS course_count FROM SC GROUP BY Sno;GROUP BY执行后,SELECT子句里只能出现GROUP BY里的列和聚合函数。标准SQL这么规定,MySQL默认不强制,但结果是非分组列随机取一个值,看着不报错,数据却是脏的。建议把MySQL的sql_mode加上ONLY_FULL_GROUP_BY,宁可让数据库报错,也别拿随机值当结果。
分组后再过滤用HAVING。它和WHERE的分工是:WHERE在分组前筛行,HAVING在分组后筛组。
-- 平均成绩超过85分的学生,按平均分降序 SELECT Sno, AVG(Grade) AS avg_grade FROM SC GROUP BY Sno HAVING AVG(Grade) > 85 ORDER BY avg_grade DESC;WHERE里不能放聚合函数。想写WHERE COUNT(*) > 2的人,数据库会直接报错,因为WHERE执行时分组还不存在,聚合条件只能放HAVING。
4.3 多表连接:INNER JOIN 与 LEFT JOIN 的取舍
多表查询在教材里叫连接。日常最常用的是等值连接:两张表按连接条件把行拼起来。连接条件没写或写错,就会生成笛卡尔积,Student表50行和SC表200行无条件下连接,结果是10000行。
-- 查询选修C001课程的学生姓名和成绩 SELECT Student.Sname, SC.Grade FROM Student INNER JOIN SC ON Student.Sno = SC.Sno WHERE SC.Cno = 'C001';INNER JOIN只保留两表都能匹配上的行。ON是连接条件,WHERE是结果过滤,写SQL时分开写,语义才清楚。表多了以后一定要用别名:
SELECT s.Sname, sc.Grade FROM Student s INNER JOIN SC sc ON s.Sno = sc.Sno WHERE sc.Cno = 'C001';表别名是给表起临时名字,列引用时带前缀能避免Sno这种同名列分不清。LEFT JOIN处理「主表要全保留」的场景,比如查所有学生的选课情况,没选课的也要显示:
SELECT s.Sname, sc.Cno, sc.Grade FROM Student s LEFT JOIN SC sc ON s.Sno = sc.Sno;没选课的学生,右表列会补NULL。LEFT JOIN里有一个高频坑:把右表的过滤条件放进WHERE,会把那些右表NULL的行滤掉,左连接悄悄退化成内连接:
-- 错误示范:想只看选了C001的,结果没选课的学生也被滤掉了 SELECT s.Sname, sc.Cno, sc.Grade FROM Student s LEFT JOIN SC sc ON s.Sno = sc.Sno WHERE sc.Cno = 'C001'; -- 正确示范:过滤条件放进ON,左表全保留 SELECT s.Sname, sc.Cno, sc.Grade FROM Student s LEFT JOIN SC sc ON s.Sno = sc.Sno AND sc.Cno = 'C001';这个差别不看执行结果很难发现,我见过太多次线上报表因为这个原因悄无声息地少行。
4.4 子查询与窗口函数:从教材进阶到生产
子查询是把一条SELECT嵌进另一条SELECT的WHERE里。查所有选修了C001课程的学生:
SELECT Sno, Sname FROM Student WHERE Sno IN (SELECT Sno FROM SC WHERE Cno = 'C001');IN适合子查询结果集小的情况。结果集大或需要逐行关联时,用EXISTS相关子查询:
SELECT s.Sno, s.Sname FROM Student s WHERE EXISTS ( SELECT 1 FROM SC sc WHERE sc.Sno = s.Sno AND sc.Cno = 'C001' );EXISTS子查询对每一行学生都执行一次,看到sc.Sno = s.Sno就知道它和外层行有关联,这就是相关子查询。EXISTS不一定比JOIN快,但语义直观,笔试里爱考。
窗口函数是解决「分组内排名」的利器。教材第4章的SQL语言1通常不展开,但面试和工作里都在用。我当年把它当黑匣子用,后来发现核心就一句话:
-- 每个学生按成绩从高到低排名 SELECT Sno, Cno, Grade, ROW_NUMBER() OVER (PARTITION BY Sno ORDER BY Grade DESC) AS rnk FROM SC;PARTITION BY负责开窗分组,ORDER BY在窗内排序,ROW_NUMBER()给每行编号。RANK()和ROW_NUMBER()的区别是:成绩并列时RANK会跳号,比如1、1、3,ROW_NUMBER不会并列,永远是1、2、3。查每组的Top N问题,窗口函数比自连接好写一个量级,也更好维护。
5. 避坑指南:SQL初学者最容易翻车的五个细节
5.1 空值比较查不出数据,COUNT却把空值算进行数
现象:WHERE Grade = NULL查不出任何行;COUNT()和COUNT(Grade)结果对不上。 原因:SQL里的NULL不是值,是「未知」。任何和NULL做等值比较的结果都是unknown,不满足WHERE的true条件;COUNT()数所有行,COUNT(Grade)跳过NULL行。 解决:判空统一用IS NULL或IS NOT NULL;统计有效成绩用COUNT(Grade);数据清洗阶段先查一下空值的形态,是NULL还是空字符串,两者要分开处理。
5.2 UPDATE、DELETE忘写WHERE,全表数据被洗
现象:本来只想改一个学生的系别,跑完发现全校都转了CS。 原因:DML不带WHERE默认作用于全表。MySQL默认autocommit,语句执行完直接提交,没有后悔药。 解决:执行更新删除前,先写同条件SELECT看命中行数;生产环境的UPDATE和DELETE先BEGIN,确认无误再COMMIT;DELETE之前先SELECT COUNT(*)确认范围。这个习惯不值钱,但保命。
5.3 多表JOIN漏了ON,几十行表拼出几千行
现象:Student表50行和SC表200行做连接,结果返回了10000行。 原因:JOIN语法里漏掉ON,或者ON里写错列名,数据库执行的就是笛卡尔积,把两个集合的所有组合全拼出来。 解决:写完JOIN立即核对ON条件;关联查询跑之前先看两张表的大致行数;用EXPLAIN看执行计划,rows列能直接暴露连接没走索引。笛卡尔积不是玄学,它就是没写连接条件。
5.4 GROUP BY后SELECT非分组列,结果看着正常其实是脏数据
现象:GROUP BY Sno却SELECT了Sname,MySQL不报错,Sname看起来也有值。 原因:MySQL默认没开ONLY_FULL_GROUP_BY。标准SQL不允许SELECT非分组列,宽松模式下数据库取的是组内任意一行的值,这个值是随机的。 解决:给MySQL设置sql_mode='ONLY_FULL_GROUP_BY',让数据库在写错的瞬间报错;写GROUP BY语句时记住,SELECT后面只能跟分组列和聚合函数。
5.5 主表删除被外键拦住,报错却不知道去哪查
现象:DELETE FROM Student WHERE Sno='2024001',数据库报外键约束失败,删不掉。 原因:SC表里还有这个学号的选课记录。外键默认行为是RESTRICT,拒绝删除被引用的父表行。 解决:先删子表引用,再删父表行:
DELETE FROM SC WHERE Sno = '2024001'; DELETE FROM Student WHERE Sno = '2024001';设计时若确定要级联清理,可以用ON DELETE CASCADE,但用之前要想清楚:一条DELETE会连带清掉多少子表数据。生产环境我一般不用级联删除,宁可在应用层分批处理,给自己留检查点。
6. 用一个综合查询验证你学没学会:从题目到SQL再到执行计划
设计一个综合题:查CS系里选课门数超过2门、平均成绩不低于85分的学生,输出学号、姓名、平均成绩,按平均分降序。这个题把WHERE、JOIN、GROUP BY、HAVING、ORDER BY全部串起来,正好覆盖第4章SQL语言1的核心内容:
SELECT s.Sno, s.Sname, AVG(sc.Grade) AS avg_grade FROM Student s JOIN SC sc ON s.Sno = sc.Sno WHERE s.Sdept = 'CS' GROUP BY s.Sno, s.Sname HAVING COUNT(sc.Cno) > 2 AND AVG(sc.Grade) >= 85 ORDER BY avg_grade DESC;执行顺序是:FROM先定位两张表并连接,WHERE过滤出CS系,GROUP BY按学号分组,HAVING过滤出选课门数大于2且平均分达标的分组,SELECT投影列并计算平均分,ORDER BY最后排序。写复杂查询时脑子里过一遍这个顺序,能少走很多弯路。
验证分两步。第一步拆开验证:先单独跑WHERE看CS系有多少学生,再单独跑GROUP BY看每个学生选课门数和平均分,最后把条件合起来。第二步用EXPLAIN看执行计划:
EXPLAIN SELECT s.Sno, s.Sname, AVG(sc.Grade) AS avg_grade FROM Student s JOIN SC sc ON s.Sno = sc.Sno WHERE s.Sdept = 'CS' GROUP BY s.Sno, s.Sname HAVING COUNT(sc.Cno) > 2 AND AVG(sc.Grade) >= 85 ORDER BY avg_grade DESC;EXPLAIN输出里最值得看的是type和rows。type显示ALL表示全表扫描,这是慢SQL优化最常见的起点;显示eq_ref或ref说明关联列走了索引。SC表作为高频关联表,Sno和Cno上应该建索引:
CREATE INDEX idx_sc_sno ON SC(Sno); CREATE INDEX idx_sc_cno ON SC(Cno);这也是慢SQL优化里的常规动作:先看执行计划是不是全表扫描,再看连接列有没有索引,最后才考虑改写SQL。不要一上来就堆索引,索引不是越多越好,写多读少和写少读多的表,索引策略完全不同。
我自己的习惯是:任何一句超过三行的SQL,先格式化再执行;任何更新删除,先看影响行数;去生产环境之前,把SQL丢到预发库跑一遍EXPLAIN。第4章的SQL语言1是整个数据库课程的地基,把建表、增删改查、约束和连接练到条件反射,后面学事务、学权限、学数据库恢复,才有东西可以踩在上面。希望这篇能帮你在敲命令时少绕几个弯。
本文还有配套的精品资源,点击获取