简介:《数据库原理(第四版)》配套课件以PPT形式呈现,面向数据库课程初学者、高校学生及希望梳理基础理论的从业人员,帮助建立从数据库系统开发流程到数据模型设计的整体认知。压缩包内共1个文件,为一份PPT演示文稿,大小约289KB,适合课堂投屏、章节预习与考前复习使用。课件围绕绪论与数据模型展开,涵盖需求分析、概念结构设计、逻辑结构设计与物理结构设计等开发环节,并讲解数据字典、数据独立性等基本概念;重点比较层次模型、网状模型和关系模型,其中网状模型部分细述基本层次联系、记录类型与字段、一对多联系、排序字段与码字段,以及查询、插入、删除、更新等数据操纵和DBTG完整性约束,并配学生、宿舍、教师、教研室及家庭关系等图示,便于理解复杂数据关系。已有168人学习,适合作为数据库原理入门与课堂配套参考。
1. 一份课件真正的用法,是把它拆成三样东西
某实验室新来的助教接手一门 48 学时的课,手里只有一份数据库原理(第四版)课件——从数据模型一路讲到恢复技术,每页都正确。第一年他照着讲,期末卷面不难看,可让学生现场写一个带分组统计的三表连接,一半人卡在非聚合列的写法上。问题不在课件,在于把课件当成了讲稿。这份数据库原理(第四版)课件本质是一张骨架:概念层给判据,SQL 层给语法,理论层给推导路径,工程层给并发与恢复的边界。真正要补的是三样它不直接给的东西——能跑的最小数据集、能手推一遍的函数依赖演算、能对照的结果预期。下面就把这三样怎么长出来讲清楚,顺带说说哪些地方最容易翻车。
2. 课件四大模块的取舍:把 48 学时压成能讲完的顺序
2.1 先把课件目录翻译成一张能力清单
多数版本的数据库原理课件按十二讲左右铺开,页数看着吓人,但落到"学生结课后能做什么",其实只有四类能力。我习惯先做一张映射表,把课件里"给了什么"和"要会什么"分开写,删减才有依据,不然容易在存储结构那几页上耗掉两节课。
| 模块 | 课件通常给到 | 实际要练成 | 建议学时占比 |
|---|---|---|---|
| 数据模型与关系代数 | 定义、图示、运算符号表 | 把自然语言需求翻成关系代数式,再翻成 SQL | 15% |
| SQL 与查询 | 语法清单、单表例题 | 多表连接、分组过滤、子查询、读执行计划 | 30% |
| 关系数据理论 | Armstrong 公理、范式定义 | 求候选码、判范式、做无损分解 | 25% |
| 事务并发与恢复 | 时间轴图、封锁协议 | 复现脏读与幻读、看懂日志与检查点 | 20% |
| 设计与物理存储 | ER 图、B+ 树结构 | 落成 DDL、选索引、估代价 | 10% |
这张表最直接的用处是砍内容。B+ 树的分裂合并过程,课件上画了五六页,考试顶多考一次插入后的节点状态,讲清"为什么树的层数低、为什么范围查询友好"两个结论就够,剩下的当课后阅读。反过来,关系代数不能砍,它决定了学生后面写 SQL 是有章法还是背模板。
2.2 为什么我一般把 SQL 提到范式前面
课件通常按"模型 → 关系代数 → SQL → 关系理论 → 设计 → 事务"排,教材逻辑严密,但课堂效果不好。学生还没见过一张真实的三表连接长什么样,就要判断dept → dloc是不是传递依赖,抽象得没有着力点。
我的做法是把 SQL 模块提前,先让学生跑出一个有冗余、有更新异常的库,再回来讲范式。具体操作是:第一周就带他们建一个选课库,故意让系名和系主任姓名同时存在学生表和另一张表里,然后现场演示"改一个系主任名字要更新三十行,漏一行就前后不一致"。这时候再讲 3NF,学生会自己问"那该怎么拆",教学阻力小很多。
顺序调整为:数据模型 → SQL 基础与多表查询 → 函数依赖与范式 → 数据库设计 → 事务与并发 → 恢复。代价是关系代数要压缩到两学时,只讲选择、投影、连接、除四个运算,够用。
2.3 学时不够时的裁剪线:哪些能删,哪些一个都不能少
真到了排不下的时候,我的裁剪优先级是这样的。
可以压缩的:物理存储章节里索引的页结构细节、恢复技术里日志格式的字节布局、并发控制里多种封锁协议的全部分类(讲清两段锁和一种协议即可)。这些内容学生工作后查文档就能补,记住结论比记住细节有用。
不能压缩的,有三样。一是候选码求解,这是范式题的入口,求错了后面全错,而且它训练的是"从函数依赖集合反推键"的思维,直接对应现实里的主键设计。二是无损连接判定的表格法,这是唯一一个能让学生动手验证"拆表拆得对不对"的工具。三是隔离级别与三类异常的对应关系,工作中排查并发问题时,第一句话问的就是"你用的哪个隔离级别"。
裁剪之后课时更紧,但每一节都指向一个可验证的动作,学生的作业从"抄定义"变成"交结果",反馈质量完全不一样。
3. 从课件例题到可跑实验:建库建表到多表查询的完整脚本
3.1 选一个贯穿全课的小型业务库:三张表定生死
课件里的例题往往是零散的:这一页讲投影用一个表,下一页讲连接换一个表。学生学完不知道这些表怎么凑到一起。我的做法是全程只用一个库——学生选课,四张表足够覆盖全课:学生、课程、选课记录,再加一张系部表用来演示传递依赖。
表少的好处是学生记得住字段名,写题时不用反复翻例题;坏处是例子会重复,所以要靠不同的查询形态来撑住内容深度,同一组表从单表过滤一路练到窗口函数。
提示:不要用课件里那种
T1(A,B,C)的抽象表名做实验,抽象表适合讲理论,不适合练 SQL,学生会把精力花在记字段含义上。
3.2 建库建表与约束:把课件里的 ER 图落成 DDL
下面这段是实验一的开场脚本,直接可跑。注意看几个刻意加进去的细节,它们是后面排错的伏笔。
-- 字符集一次性定死,避免中文姓名在排序、比较时出怪结果 CREATE DATABASE IF NOT EXISTS course_demo DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_0900_ai_ci; USE course_demo; CREATE TABLE student ( sno CHAR(8) NOT NULL COMMENT '学号,定长,取值稳定', sname VARCHAR(20) NOT NULL, gender CHAR(1) NOT NULL DEFAULT 'U', dept VARCHAR(30) NOT NULL, PRIMARY KEY (sno), CONSTRAINT ck_gender CHECK (gender IN ('M','F','U')) ) ENGINE=InnoDB; CREATE TABLE course ( cno CHAR(5) NOT NULL, cname VARCHAR(40) NOT NULL, credit DECIMAL(3,1) NOT NULL, teacher VARCHAR(20) NOT NULL, PRIMARY KEY (cno), CONSTRAINT ck_credit CHECK (credit > 0 AND credit <= 10) ) ENGINE=InnoDB; CREATE TABLE enrollment ( sno CHAR(8) NOT NULL, cno CHAR(5) NOT NULL, term CHAR(6) NOT NULL COMMENT '形如 2024-1,允许重修同一门课', score DECIMAL(5,2) NULL COMMENT 'NULL 表示未出分', PRIMARY KEY (sno, cno, term), KEY idx_cno (cno), CONSTRAINT fk_e_sno FOREIGN KEY (sno) REFERENCES student(sno) ON DELETE CASCADE, CONSTRAINT fk_e_cno FOREIGN KEY (cno) REFERENCES course(cno) ON DELETE RESTRICT, CONSTRAINT ck_score CHECK (score IS NULL OR (score >= 0 AND score <= 100)) ) ENGINE=InnoDB;逻辑说明:三张表对应 ER 图里的两个实体加一个多对多联系。选课表的主键必须是(sno, cno, term)三元组,因为同一个学生在不同学期可以重修同一门课,只用(sno, cno)会把重修记录挤掉——这是课件例题里最常见的简化,也是学生建表时最常照抄出错的地方。
参数说明:sno用CHAR(8)而不是VARCHAR,学号长度固定,定长在索引里更紧凑;score允许NULL,把"未出分"和"0 分"区分开,否则统计平均分时会把没考的算成零分;ON DELETE CASCADE用在外键引用学生的地方,学号删了选课记录跟着删,ON DELETE RESTRICT用在课程上,有选课记录的课程不允许直接删,强制学生先处理依赖。CHECK约束在较新版本的 MySQL 上才真正生效,老版本只解析不执行,实验前让学生用一句SELECT VERSION();确认自己环境,比事后解释为什么脏数据能插进去省事。
还有一个容易被跳过的步骤:插完数据后让学生故意插一条score = 120的记录,看约束报什么错;再插一条sno不存在的选课记录,看外键报什么错。错误信息本身就是最好的教学材料,比讲十遍"约束保证完整性"管用。
3.3 多表查询的四个台阶:从笛卡尔积到窗口函数
课件的 SQL 例题通常一步到位给答案,学生看得懂写不出。拆成四个台阶,每阶只加一个新概念,逐阶跑通再合并。
第一阶,笛卡尔积加连接条件,让学生自己感受不加条件的后果。
-- 不加 WHERE 会得到 学生数 x 课程数 x 选课数 行,先跑一次看行数 SELECT COUNT(*) FROM student, course, enrollment; -- 加上连接条件,行数才回到选课记录数 SELECT s.sname, c.cname, e.score FROM student s JOIN enrollment e ON e.sno = s.sno JOIN course c ON c.cno = e.cno WHERE e.term = '2024-1';第二阶,外连接找"没发生的事",这是课件讲LEFT JOIN时很少给足练习的类型。
-- 找出 2024-1 学期一门课都没选的学生 SELECT s.sno, s.sname FROM student s LEFT JOIN enrollment e ON e.sno = s.sno AND e.term = '2024-1' WHERE e.sno IS NULL;逻辑说明:学期条件必须写在ON里,不能挪到WHERE。挪过去之后,未匹配的行e.term为NULL,NULL = '2024-1'判定为未知,这些行会被过滤掉,外连接退化成内连接,结果直接空掉。这是学生作业里出现频率最高的一类错误。
第三阶,分组与过滤,把聚合条件和行条件分清。
-- 每门课的选课人数与平均分,只保留选课满 3 人的课 SELECT c.cno, c.cname, COUNT(e.sno) AS cnt, ROUND(AVG(e.score), 2) AS avg_score FROM course c LEFT JOIN enrollment e ON e.cno = c.cno AND e.score IS NOT NULL GROUP BY c.cno, c.cname HAVING COUNT(e.sno) >= 3 ORDER BY avg_score DESC;参数说明:GROUP BY里带上cname,是因为在ONLY_FULL_GROUP_BY模式下,非聚合列必须出现在分组列表里;虽然引擎能识别主键函数依赖,但跨表连接时这个推导不一定成立,老实写全最稳。WHERE过滤行,HAVING过滤组,选课人数是组级指标,只能放HAVING。AVG会自动跳过NULL,但COUNT(e.sno)统计的是非空学号,两者口径要对齐,否则"平均分很高但人数为 0"这种结果会让学生困惑很久。
第四阶,窗口函数,给每个学生取成绩最好的两门课。这一步在很多课件里压根没有,但它是从"会写 SQL"到"能做分析"的分水岭。
-- 每个学生按成绩降序排名,取前两名;成绩相同按课程号稳定排序 SELECT sno, cno, score, rk FROM ( SELECT e.sno, e.cno, e.score, ROW_NUMBER() OVER ( PARTITION BY e.sno ORDER BY e.score DESC, e.cno ) AS rk FROM enrollment e WHERE e.score IS NOT NULL ) t WHERE rk <= 2;逻辑说明:窗口函数在WHERE之前计算,所以排名不能直接写在WHERE里,必须套一层子查询。ROW_NUMBER遇到同分也给不同名次,如果业务上允许并列,换成RANK或DENSE_RANK,这三个函数的差别值得让学生各跑一次对比。
3.4 用 EXPLAIN 把"能跑"变成"能解释"
课件讲查询优化通常只给结论,学生在自己机器上看不到差别。让他们对着同一份数据跑两条等价查询,再看执行计划,印象完全不一样。
-- 写法一:在列上套函数,索引失效 EXPLAIN SELECT * FROM enrollment WHERE YEAR(term) = 2024; -- 写法二:改成范围条件,索引可用 EXPLAIN SELECT * FROM enrollment WHERE term >= '2024-1' AND term < '2025-1';看三个字段就够:type从ALL变成range,说明用上了索引;rows是预估扫描行数,两条查询差一个数量级就值得追问;filtered表示扫描后还剩多少比例,太低说明条件选择性差。Extra里出现Using filesort或Using temporary时,提示学生回去看ORDER BY和GROUP BY的列有没有索引覆盖。
参数说明上要提醒一句:rows是优化器基于统计信息的估算,数据量小的时候两条查询可能都显示扫全表,这不代表索引没用,而是优化器判断走索引更贵。实验数据至少插到几千行再观察,否则结论全是玄学。
4. 函数依赖与范式推导:课件里最抽象一节的可操作补丁
4.1 求候选码的两条手算路径与适用边界
候选码求解是整章的地基,课件一般只给闭包定义,学生看完不知道从哪下手。我教两条路径,让他们按属性数量选。
第一条是属性分类法,适合属性在六个以内的题。把所有属性按在函数依赖左右出现的情况分四类:只在左边出现的记为 L,只在右边出现的记为 R,两边都出现的记为 LR,都不出现的记为 N。规则是:候选码一定包含 L 类和 N 类的全部属性,一定不包含 R 类。先算(L ∪ N)的闭包,如果等于全集,它就是唯一候选码;否则从 LR 类里逐个(以及两两组合)加进去试。
举个例子,R(A,B,C,D),F = {AB→C, C→D, D→A}。L 类是{B},R 类是空集,LR 类是{A,C,D},N 类是空集。先算B+ = {B},不等于全集,所以要把 LR 里的属性加进来试:AB+ = {A,B,C,D}成立,BC+ = {B,C,D,A}成立,BD+ = {B,D,A,C}成立。三个候选码AB、BC、BD,所有属性都是主属性,不存在非主属性对码的部分依赖和传递依赖,这个模式直接就是 BCNF。
第二条是闭包逐步展开法,适合属性多、依赖链长的题。从一个属性集出发,反复扫依赖集,能把右边加进来就加,直到集合不再变化。这个方法慢但不容易漏,考试时用作验算。两条路径都教,学生自己会形成偏好,但要强调一件事:LR 类的属性不能一上来就塞进候选码,塞多了会算出多余的"超码",这是作业里最常见的失分点。
4.2 从 1NF 到 BCNF:一份关系模式的逐级拆解实录
课件讲范式定义时是一条条列,学生记住了定义但不会用。拿选课库的一个反例模式走一遍,比讲定义有效得多。
原始模式:SC(sno, sname, dept, dloc, cno, cname, credit, score),函数依赖集为sno→sname、sno→dept、dept→dloc、cno→cname、cno→credit、(sno,cno)→score。候选码是(sno, cno)。
| 阶段 | 判断依据 | 违反的依赖 | 处理动作 |
|---|---|---|---|
| 1NF | 每个属性不可再分 | 无 | 已经是 1NF,无需处理 |
| 2NF | 非主属性完全依赖于码 | sno→sname、sno→dept、cno→cname、cno→credit都是部分依赖 | 按码的左部拆成三张表 |
| 3NF | 非主属性不传递依赖于码 | 拆完后dept→dloc在学生表内形成传递依赖 | 把dept和dloc单独成表 |
| BCNF | 每个决定因素都包含候选码 | 学生表内sno→dept,sno是码,满足;系部表内dept→dloc,dept是码,满足 | 已满足 |
拆完的结果是四张表:student(sno, sname, dept)、dept_info(dept, dloc)、course(cno, cname, credit)、enrollment(sno, cno, score)。这个过程要让学生自己动手写,写完之后追问一句:拆成四张表之后,查询"某学生的系主任是谁"要多一次连接,这个代价换来的是什么?答案是更新异常消失——改一个系的系主任姓名只改一行。范式不是越高级越好,是拿连接代价换更新一致性,这个权衡关系必须在课堂上点破,否则学生会以为范式等级越高越正确。
4.3 模式分解的两个判据:无损连接与保持依赖怎么验
拆表拆得对不对,靠两个判据。课件上给了定义和公式,学生不会算。无损连接用表格法,纸上就能做,我要求每道题都画出这张表。
判定R(A,B,C),F = {A→B, B→C},分解为R1(A,B)和R2(A,C)是否无损。先建初始表,行对应分解后的关系,列对应原属性,某属性在该关系中就填a下标,否则填b下标。
| 关系 | A | B | C |
|---|---|---|---|
| R1 | a1 | a2 | b13 |
| R2 | a1 | b21 | a3 |
按A→B处理:两行的 A 列都是a1,属于同值,把 B 列的b21改成a2。再按B→C处理:两行 B 列都是a2,把b13改成a3。此时 R1 那一行变成(a1, a2, a3),整行都是a,判定无损。
保持依赖的检查更简单:把函数依赖集里的每条依赖拿出来,看它的左右属性是否落在同一个分解后的关系里。A→B在R1内,可检查;B→C需要 B 和 C 同表,R1没有 C,R2没有 B,这条依赖丢失,判定为不保持依赖。
这个例子的价值在于它给出了一个反直觉结论:无损连接和保持依赖是两件事,能同时满足最好,但很多时候只能保一个。实际工程里我更看重无损连接,因为它保证拆完再连接不会多出行或少出行;保持依赖影响的只是约束能不能在单表内检查,可以用触发器或应用层校验兜住。这个取舍讲清楚,学生对"范式理论有什么用"的疑问基本就消了。
5. 避坑与排查:课件改造、实验环境、版本对齐的五条血泪记录
5.1 现象:课件上的 SQL 拷到自己电脑就报错
现象:例题里的SELECT * FROM student WHERE sname LIKE '张%'能跑,但分组统计那条一执行就报"非聚合列不在 GROUP BY 中",或者CHECK约束完全不生效。
原因有两个。一是严格模式差异,较新版本的 MySQL 默认开启ONLY_FULL_GROUP_BY,而课件配图往往是在宽松模式下截的;二是CHECK约束的执行能力跟版本直接相关,老版本只解析不落实。
解决:上课前统一环境,让学生执行SELECT @@sql_mode;记录自己的模式,实验脚本里显式写全GROUP BY的列,不依赖任何模式的宽容。约束相关的实验,先用SELECT VERSION();确认版本,低版本环境改用触发器替代,或者干脆把这条实验挪到理论课上讲。
5.2 现象:多对多关系建完表,查出来的选课人数翻倍
现象:COUNT出来的选课人数是实际的两倍,或者某个学生的平均分被拉低。
原因:中间表的主键定义不完整。学生照课件的 ER 图建了(sno, cno)联合主键,但业务上允许重修,同一对(sno, cno)出现多行,插入时被主键挡住,于是有人干脆去掉主键,结果重复数据全进来了,连接后行数翻倍。
解决:中间表主键必须包含能唯一标识一次选课行为的全部属性,选课场景里是(sno, cno, term)。建表后立刻做一次去重校验:SELECT sno, cno, term, COUNT(*) FROM enrollment GROUP BY 1,2,3 HAVING COUNT(*) > 1;,结果为空才算过关。这条校验我一般直接写进实验的验收脚本里。
5.3 现象:范式练习题算出的候选码和课件答案对不上
现象:学生算出三个候选码,课件答案只给两个,或者反过来,多算出一个超码。
原因:LR 类属性被无脑塞进了候选码,或者闭包计算中途停止得太早。还有一种情况是函数依赖集本身有冗余依赖,课件没做最小化,学生按原集算就出了偏差。
解决:强制两步走。第一步先求最小依赖集,去掉右边多余属性和多余依赖;第二步再跑属性分类法。算完之后必须验算:每个候选码的闭包要等于全集,且任何真子集的闭包都不等于全集。验算这一步写进作业要求,抄答案的学生过不了这一关。
5.4 现象:事务隔离级别的演示看不到任何异常
现象:讲脏读时,两个会话按课件的时间轴操作,第二个会话死活读不到未提交的数据,学生开始怀疑理论是错的。
原因有三个常见来源:会话处于自动提交模式,每条语句单独成事务,时间轴根本没重叠;表用的是不支持事务的存储引擎,隔离级别设置形同虚设;两个会话连的是不同库或者不同连接池,压根不在一个事务上下文里。
解决:实验前先跑三句确认——SELECT @@autocommit;关掉自动提交,SHOW TABLE STATUS LIKE 'enrollment';确认引擎是 InnoDB,SELECT @@transaction_isolation;确认隔离级别。然后两个会话严格按BEGIN; ... COMMIT;的节奏走。演示幻读时记得用范围查询,等值查询在可重复读下加的是记录锁,看不出间隙锁的效果,这一点课件上一般画不出来,得现场演示。
5.5 现象:课件页码和手里教材对不上,例题数据前后矛盾
现象:学生按 PPT 上标的页码翻书找不到对应内容,或者前面例题里学号是S01,后面习题里变成2024001。
原因:课件在不同版本教材之间复用,页码和例题数据没同步;也可能是多人分工做课件,各写各的数据。
解决:课件只在开头标一次"对应教材章节范围",不标具体页码,页码交给学生按章节标题自己定位。例题数据统一用一套,建一个纯文本的数据字典文件放在课程资料里,任何一页 PPT 引用字段时都以此为准。这件事看着琐碎,但它是学生信任课件的前提,前后矛盾的例子出现两次,后面的内容他们就不认真看了。
5.6 现象:实验课时间全花在环境上,SQL 没写几行
现象:两节课下来,一半人在装数据库、找客户端、处理端口冲突,真正写查询的时间不到二十分钟。
原因:本地环境差异太大,每个人的操作系统、已装软件、权限都不一样,排障成本不可控。
解决:准备一个容器化的最小环境,把建库脚本、初始数据、验收脚本全部打包进去,学生拉下来一条命令起服务。配置文件里固定字符集、隔离级别、SQL 模式三项,避免"在我机器上是好的"这类扯皮。
# 起一个固定配置的数据库实例,端口映射到本机 3307,避免和已有服务冲突 docker run -d --name dblab \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD=lab1234 \ -e MYSQL_DATABASE=course_demo \ -v "$PWD/init:/docker-entrypoint-initdb.d:ro" \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_0900_ai_ci \ --transaction-isolation=REPEATABLE-READ参数说明:-v挂载的init目录里放建表和初始化数据的.sql文件,容器首次启动时按文件名顺序自动执行,学生的环境因此完全一致;--transaction-isolation显式指定隔离级别,讲并发那一节时改这个参数重启即可对比效果;端口用 3307 是为了避开本机可能已经占用的 3306,这条踩坑经验值得单独提一句,否则第一节课就有人连错实例,还以为数据没插进去。
6. 把课件变成可验证的教学资产:三个能立刻用上的进阶技巧
第一个技巧是给每一讲配一份"预期输出"。不是答案,是结果本身的行数和前几行内容。比如多表查询那一讲,验收脚本只做三件事:跑一遍参考答案,比对行数,比对按主键排序后的前五行。学生写出来的 SQL 只要结果对得上就算过,写法可以完全不同。这样做的好处是把评价标准从"像不像标准答案"变成"结果对不对",学生敢试不同的连接顺序和子查询写法,理解反而更深。
第二个技巧是给反例。每讲结束给一份"看起来对但实际错"的材料,让学生找问题。范式那一讲给一个只做到 2NF 的分解,问他们哪里还会产生更新异常;事务那一讲给一段没有BEGIN的会话记录,问为什么隔离级别没起作用。反例比正例更能暴露理解漏洞,也最接近真实工作中排查问题的形态:你拿到的从来不是标准答案,是一段有问题的现状。
第三个技巧是把执行计划当作业交。要求每次优化类作业附上EXPLAIN的输出截图,并写一句"我改了哪个条件,type从什么变成了什么"。这一步逼着学生从"能跑"走到"知道为什么快",也是从课堂走向工程的那一小步。
| 技巧 | 交付物 | 验收方式 |
|---|---|---|
| 预期输出 | 行数 + 排序后前五行 | 脚本自动比对,允许不同写法 |
| 反例材料 | 一份有缺陷的分解或会话记录 | 学生书面指出问题并给出修正 |
| 执行计划 | EXPLAIN 输出 + 一句改动说明 | 对比优化前后的 type 与 rows |
最后说个我自己的教训。前几年我总想在一节课里把范式的来龙去脉讲透,从 Armstrong 公理推到 BCNF,讲得自己很爽,学生作业交上来一片空白。后来把推导砍掉一半,留出二十分钟让他们在纸上把那份选课模式拆一遍,再互相检查无损连接,效果立刻不一样。理论课的产出不是讲完了,是学生手上留下了一道自己算对的痕迹。这份数据库原理(第四版)课件的价值也一样,它给的是骨架,血肉得靠一套能跑、能验、能对错的数据和脚本填进去。希望帮到你。
本文还有配套的精品资源,点击获取