
简介这份资源是大连理工大学软件学院数据库系统课程的上机实验报告基于华为 OpenGauss 数据库管理系统编写面向正在学习数据库课程、需要完成上机实验或撰写实验报告的高校学生。报告围绕数据库基本操作展开涵盖 DDL 数据定义创建数据库、表、索引与视图、DML 数据操作插入、更新、删除、数据查询单表查询、聚合查询、多表查询、子查询与集合查询、索引操作以及事务的并发控制等核心实验模块并配有预备知识、实验任务与 SQL 代码及对应结果便于对照理解与复盘。资源包共 1 个 docx 文件约 1.36MB结构完整、条理清晰可直接作为实验报告模板或复习参考。目前已有 619 人学习下载适合需要系统掌握 OpenGauss 基本操作、查漏补缺的数据库初学者与备考学生使用。1. 大连理工软件数据库 OpenGauss 上机作业一份能直接跑通的实验报告拆解如果你正在上大连理工软件学院的数据库系统课程或者自学 OpenGauss 却找不到一套完整的实操参照这份上机作业报告值得仔细拆一遍。它覆盖了从建表、增删改查到索引、事务并发、权限回收的完整链路用的是华为 OpenGauss 作为底层数据库。和网上那些只贴几段 SQL 的零散笔记不同这份报告按实验手册的章节组织每个任务都给了可执行的 SQL 代码和预期结果相当于把整个上机流程走了一遍。适合两类人一是正在做同款实验、需要对照排查的同学二是想系统练一遍 OpenGauss 基础操作、但不想在环境配置上反复翻车的从业者。下面按「资源是什么 → 怎么用 → 坑在哪」的顺序展开。2. DDL 建表与外键约束四张表的依赖顺序不能乱2.1 为什么 DEPT 必须先于 EMP 创建这份报告的第一个实验任务是创建 DEPT、BONUS、SALGRADE、EMP 四张表。关系模式里写得很清楚EMP 表的 DEPTNO 是外键指向 DEPT 表的 DEPTNO。这意味着建表顺序有硬性依赖——DEPT 必须先存在否则 EMP 的外键约束无法引用一个不存在的目标表。OpenGauss 在处理外键引用时会在建表阶段就校验被引用表和被引用列是否存在。如果先建 EMP 再建 DEPT执行到CONSTRAINT FK_DEPTNO FOREIGN KEY (DEPTNO) REFERENCES DEPT(DEPTNO)这一句时会直接报错提示引用的表不存在。常见做法是严格按照依赖关系排序先建无外键依赖的 DEPT、BONUS、SALGRADE最后建带外键的 EMP。另一个容易忽略的点是主键约束的命名。报告里用了CONSTRAINT PK_DEPT PRIMARY KEY (DEPTNO)这种显式命名方式而不是直接写PRIMARY KEY (DEPTNO)。显式命名的好处是后续如果要删除或修改约束可以直接用约束名定位不用去查系统表猜自动生成的名称。在 OpenGauss 里不指定约束名时系统会自动生成类似emp_pkey的名字但不同版本生成规则可能有差异显式命名更稳妥。2.2 建表 SQL 的完整执行与验证把报告里的建表语句整理成可直接执行的顺序如下-- 先建无外键依赖的表 CREATE TABLE DEPT ( DEPTNO INT, DNAME VARCHAR(14), LOC VARCHAR(13), CONSTRAINT PK_DEPT PRIMARY KEY (DEPTNO) ); CREATE TABLE BONUS ( ENAME VARCHAR(10), JOB VARCHAR(9), SAL INT, COMM INT ); CREATE TABLE SALGRADE ( GRADE INT, LOSAL INT, HISAL INT ); -- 最后建带外键的 EMP 表 CREATE TABLE EMP ( EMPNO INT, ENAME VARCHAR(10), JOB VARCHAR(9), MGR INT, HIREDATE DATETIME, SAL FLOAT, COMM FLOAT, DEPTNO INT, CONSTRAINT PK_EMP PRIMARY KEY (EMPNO), CONSTRAINT FK_DEPTNO FOREIGN KEY (DEPTNO) REFERENCES DEPT(DEPTNO) );这段代码的逻辑很直接前三张表没有相互引用关系执行顺序无所谓EMP 表因为引用了 DEPT 的主键必须放在 DEPT 之后。参数上需要注意几个细节VARCHAR(14)和VARCHAR(13)是报告里指定的长度实际建表时如果字段内容不会超可以按需调整但建议保持和报告一致避免后续插入数据时因为长度不够被截断。HIREDATE用的是DATETIME类型不是DATE这一点在插入日期数据时要对应否则可能触发隐式转换。执行完可以用\d DEPT和\d EMP在 OpenGauss 的 gsql 客户端里查看表结构确认主键和外键约束都已生效。如果外键约束没建上\d EMP的输出里不会出现Foreign-key constraints段落。2.3 外键约束对后续 DML 的连锁影响建表只是开始外键约束会直接影响后面 DML 实验里的插入和删除操作。报告在 DML 部分有一个任务是「删除 DEPT 表中所有数据」执行DELETE FROM DEPT;时如果 EMP 表里还有引用 DEPT 的数据OpenGauss 会直接拒绝删除报错提示违反外键约束。这就是为什么报告在插入数据时先清空 DEPT 和 EMP再按 DEPT → EMP → SALGRADE 的顺序重新插入。清空顺序和插入顺序刚好相反先删子表数据再删父表数据。如果反过来先删 DEPT只要 EMP 里还有对应 DEPTNO 的记录删除就会被阻塞。实际做实验时如果遇到ERROR: update or delete on table dept violates foreign key constraint这类报错先检查 EMP 表里是否还有残留数据。常见做法是执行DELETE FROM EMP;再执行DELETE FROM DEPT;顺序不能颠倒。3. DML 数据操作与查询从插入到子查询的完整链路3.1 批量插入数据的字段对齐问题报告在 DML 实验里给了一段批量插入 DEPT、EMP、SALGRADE 数据的代码。这段代码看起来只是简单的 INSERT但实际执行时最容易翻车的地方是字段顺序和值类型的对齐。以 EMP 表为例插入语句是INSERT INTO EMP VALUES(7369, SMITH, CLERK, 7566, 1980-12-17, 800, NULL, 20);。这里没有指定列名值的顺序必须严格对应建表时的字段顺序EMPNO、ENAME、JOB、MGR、HIREDATE、SAL、COMM、DEPTNO。如果建表时字段顺序和报告不一致这条 INSERT 就会把值插到错误的列里而且不一定报错——比如把 SAL 的值插到 COMM 列类型都是数值数据库不会拦截但查询结果就对不上了。我一般会建议在批量插入时显式写出列名像这样INSERT INTO EMP (EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO) VALUES (7369, SMITH, CLERK, 7566, 1980-12-17, 800, NULL, 20);显式列名的好处是即使后续表结构增加了字段或者字段顺序调整了这条 INSERT 依然能正确执行。代价是代码稍微长一点但在实验场景下可复现性比简洁更重要。另外注意COMM字段插入的是NULL不是 0。报告在查询实验里有一个任务是「查询每个员工每个月拿到的总金额」用的公式是NVL(SAL,0)NVL(COMM,0)。如果插入时把 NULL 写成了 0这个查询的结果虽然也对但就体现不出 NVL 函数处理空值的意义了。NULL 和 0 在聚合函数里的行为不同SUM(COMM)会忽略 NULL 行但会把 0 算进去。3.2 单表查询里的 LIKE 和 NVL报告的单表查询部分有几个典型任务其中两个容易出问题一个是「显示第 3 个字符为大写 O 的所有员工的姓名及工资」另一个是「查询每个员工每个月拿到的总金额」。第一个任务用的是LIKE %%O%。这里报告里的写法有点特殊——%%O%在标准 SQL 里应该是__O%两个下划线匹配任意两个字符然后 O 匹配第三个字符。但报告里写的是%%O%在 OpenGauss 里%匹配任意长度字符串所以%%O%实际上等价于%O%会匹配任何位置包含 O 的姓名而不是第三个字符为 O。如果严格按照题目要求「第 3 个字符为大写 O」正确的写法应该是LIKE __O%两个下划线。-- 严格匹配第3个字符为O SELECT ENAME, SAL FROM EMP WHERE ENAME LIKE __O%; -- 报告里的写法实际匹配任意位置包含O SELECT ENAME, SAL FROM EMP WHERE ENAME LIKE %%O%;这个差异在做实验时可能不会被发现因为 SCOTT 和 JONES 这两个名字都满足「第三个字符是 O」但%%O%还会额外匹配到其他位置有 O 的名字。如果实验要求严格对照结果建议用__O%。第二个任务用NVL(SAL,0)NVL(COMM,0)计算总金额。NVL 是 OpenGauss 兼容 Oracle 语法提供的函数作用是当第一个参数为 NULL 时返回第二个参数。这里把 SAL 和 COMM 的 NULL 都转成 0 再相加避免 NULL 参与运算导致整个表达式结果为 NULL。如果不套 NVLSALCOMM在 COMM 为 NULL 时结果就是 NULL而不是 SAL 的值。3.3 聚合查询的 GROUP BY 与 HAVING 边界聚合查询部分有三个任务分别对应 GROUP BY 的基本用法、多列分组、以及 HAVING 过滤。这里的关键是理解 WHERE 和 HAVING 的执行顺序WHERE 在分组前过滤行HAVING 在分组后过滤组。报告里「显示平均工资低于 2500 的部门号平均工资及最高工资」这个任务用的是GROUP BY DEPTNO HAVING AVG(SAL)2500。如果写成WHERE AVG(SAL)2500会直接报错因为 WHERE 子句里不能使用聚合函数。这是 SQL 初学阶段最常见的翻车点之一。-- 正确HAVING 过滤分组后的聚合结果 SELECT DEPTNO, AVG(SAL) AS AVERAGE, MAX(SAL) AS MAX FROM EMP GROUP BY DEPTNO HAVING AVG(SAL) 2500; -- 错误WHERE 里不能用聚合函数 SELECT DEPTNO, AVG(SAL) AS AVERAGE, MAX(SAL) AS MAX FROM EMP WHERE AVG(SAL) 2500 GROUP BY DEPTNO;另一个细节是 SELECT 列表里的非聚合列必须出现在 GROUP BY 里。比如SELECT DEPTNO, JOB, AVG(SAL) FROM EMP GROUP BY DEPTNO, JOB是合法的因为 DEPTNO 和 JOB 都在 GROUP BY 里。如果只写GROUP BY DEPTNO但 SELECT 里出现了 JOBOpenGauss 会报错提示 JOB 不在 GROUP BY 中。这一点比 MySQL 的宽松模式严格从 MySQL 转过来的话需要特别注意。3.4 多表查询与子查询的实现差异多表查询部分覆盖了 JOIN、自连接、外连接。其中「查询 SCOTT 的上级领导的姓名」用的是自连接FROM EMP, EMP LEADER WHERE EMP.ENAMESCOTT AND EMP.MGRLEADER.EMPNO。这里把 EMP 表取了两个别名一个代表员工本身一个代表领导通过 MGR 和 EMPNO 关联。自连接的关键是别名不能省否则数据库无法区分两次引用的同一张表。「显示部门的部门名称员工名即使部门没有员工也显示部门名称」用的是 LEFT JOIN。左连接保证左表DEPT的所有行都出现右表EMP没有匹配时用 NULL 填充。如果写成 INNER JOIN没有员工的部门就不会出现在结果里。子查询部分报告特别标注了「必须使用子查询实现」。其中「显示所有员工的名称、工资以及工资级别」用的是相关子查询SELECT ENAME, SAL, (SELECT GRADE FROM SALGRADE WHERE SAL BETWEEN LOSAL AND HISAL) AS GRADE FROM EMP ORDER BY GRADE;这个子查询在 SELECT 列表里对每一行 EMP 记录执行一次用当前行的 SAL 去 SALGRADE 表里匹配等级。相关子查询的性能通常不如 JOIN但在实验场景下数据量小差异可以忽略。需要注意的是 ORDER BY 里引用了 GRADE 别名OpenGauss 支持在 ORDER BY 中使用 SELECT 列表里的别名。4. 索引、事务并发与权限三个最容易踩坑的实验环节4.1 索引创建后查询计划的变化索引操作实验的核心是验证索引对查询性能的影响。报告里的任务通常是创建一个索引然后用 EXPLAIN 查看查询计划对比创建前后是否走了索引扫描。在 OpenGauss 里创建索引的语法是CREATE INDEX idx_name ON table_name(column_name);。创建之后用EXPLAIN SELECT * FROM EMP WHERE DEPTNO10;查看执行计划。如果索引生效计划里会出现Index Scan或Bitmap Index Scan而不是Seq Scan。但这里有一个常见的误解不是建了索引就一定会走索引。如果表的数据量很小比如实验里的十几行数据优化器可能判断全表扫描比索引扫描更快依然选择 Seq Scan。这不是索引没建成功而是优化器的成本估算结果。想验证索引确实可用可以临时关闭顺序扫描SET enable_seqscan off;再执行 EXPLAIN这时应该能看到索引扫描的计划。另一个坑是索引列上的隐式类型转换。如果 DEPTNO 是 INT 类型查询写成WHERE DEPTNO10虽然 OpenGauss 会做隐式转换但可能导致索引失效。常见做法是保持查询条件和列类型一致INT 列就用 INT 值去查。4.2 事务并发控制的隔离级别验证事务并发控制实验是这份报告里最有实操价值的部分之一。报告通过设计并发场景让两个会话同时操作同一张表观察不同隔离级别下的行为差异。OpenGauss 默认的隔离级别是 READ COMMITTED。在这个级别下一个事务只能看到其他事务已经提交的修改。如果会话 A 更新了一行但还没提交会话 B 查询这行时看到的还是旧值。报告里的实验通常会设计这样的场景会话 A 执行BEGIN; UPDATE EMP SET SAL9999 WHERE EMPNO7369;但不提交会话 B 执行SELECT SAL FROM EMP WHERE EMPNO7369;观察 B 读到的是旧值还是新值。验证时需要注意OpenGauss 的 gsql 客户端需要开两个独立会话。常见做法是开两个终端窗口分别用 gsql 连接同一个数据库。在一个窗口里执行 BEGIN 和 UPDATE另一个窗口里执行 SELECT。如果 B 窗口的查询被阻塞说明可能遇到了行锁等待——A 没提交时B 对同一行的更新操作会等待但普通 SELECT 在 READ COMMITTED 下不会被阻塞。如果想验证更高级别的隔离可以用SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;或SERIALIZABLE。REPEATABLE READ 下同一事务内多次查询同一行会看到相同的结果即使其他事务已经提交了修改。SERIALIZABLE 则更严格可能触发序列化失败需要应用层重试。4.3 权限授予与回收的连锁反应权限设置实验覆盖了系统权限、对象权限的授予和回收。报告里的任务包括把权限授予用户或角色、把角色权限授予其他角色、以及权限回收。在 OpenGauss 里创建用户用CREATE USER user1 WITH PASSWORD password;授予系统权限用GRANT CREATE ON DATABASE dbname TO user1;授予对象权限用GRANT SELECT ON TABLE emp TO user1;。回收权限用REVOKE。这里最容易踩的坑是权限的级联回收。如果用户 A 把权限授予了用户 B并且带了WITH GRANT OPTIONB 又把权限授予了 C。当 A 回收 B 的权限时C 的权限也会被级联回收。如果不希望级联需要在回收时加RESTRICT选项如果数据库支持。OpenGauss 的默认行为是级联回收这一点在做权限实验时要特别注意否则可能把预期外的权限也收掉。另一个坑是角色和用户的区别。在 OpenGauss 里角色和用户本质上都是可以拥有权限的实体区别在于用户默认可以登录角色默认不能。把权限授予角色后需要把角色再授予用户用户才能实际获得这些权限。报告里的实验步骤通常会把这两步分开执行时不要漏掉角色授予用户这一步。5. 避坑与排查五个高频翻车现场5.1 建表时外键引用报错「relation does not exist」现象执行 EMP 建表语句时报错ERROR: relation dept does not exist。原因DEPT 表还没创建或者创建在了不同的 schema 下。OpenGauss 默认使用 public schema如果 DEPT 建在了其他 schemaEMP 的外键引用需要写成REFERENCES schema_name.dept(deptno)。解决先确认 DEPT 表已存在用\dt查看当前 schema 下的所有表。如果 DEPT 在别的 schema要么把 EMP 也建在同一 schema要么在外键引用里加上 schema 前缀。5.2 插入日期数据时报格式错误现象执行INSERT INTO EMP VALUES(7369,SMITH,CLERK,7566,1980-12-17,800,NULL,20);时报错ERROR: invalid input syntax for type timestamp。原因HIREDATE 字段定义的是 DATETIME 类型但插入的字符串格式和数据库预期的日期格式不匹配。OpenGauss 默认的日期格式可能是YYYY-MM-DD HH:MI:SS只写日期部分在某些配置下可以在某些配置下会报错。解决用TO_DATE(1980-12-17,YYYY-MM-DD)显式转换或者确认数据库的DateStyle参数。用SHOW DateStyle;查看当前设置如果是ISO, MDY1980-12-17这种格式通常可以识别。5.3 GROUP BY 报错「column must appear in the GROUP BY clause」现象执行SELECT DEPTNO, JOB, AVG(SAL) FROM EMP GROUP BY DEPTNO;时报错提示 JOB 不在 GROUP BY 中。原因SELECT 列表里出现了非聚合列 JOB但 GROUP BY 里只有 DEPTNO。OpenGauss 要求 SELECT 列表里的非聚合列必须全部出现在 GROUP BY 里。解决把 JOB 加到 GROUP BY 里写成GROUP BY DEPTNO, JOB。如果业务上确实不需要按 JOB 分组就把 JOB 从 SELECT 列表里去掉。5.4 事务实验时会话被阻塞现象在会话 B 里执行 UPDATE 或 DELETE 时一直等待不返回结果。原因会话 A 里有一个未提交的事务持有行锁。会话 B 试图修改同一行需要等待 A 释放锁。解决在会话 A 里执行COMMIT;或ROLLBACK;释放锁。如果找不到是哪个会话持有锁可以查pg_locks视图SELECT * FROM pg_locks WHERE NOT granted;找到阻塞的 pid 后用pg_terminate_backend(pid)终止。5.5 权限回收后用户仍然能访问现象执行REVOKE SELECT ON emp FROM user1;后user1 仍然能查询 emp 表。原因user1 可能通过角色继承了权限。如果 user1 是某个角色的成员而该角色有 emp 表的 SELECT 权限回收 user1 的直接权限不会影响角色继承的权限。解决检查 user1 的角色成员关系\du user1查看角色归属。如果需要彻底回收要么把 user1 从角色中移除要么回收角色的权限。6. 进阶技巧用 EXPLAIN ANALYZE 验证索引与事务的实际行为做完基础实验后如果想进一步验证索引和事务的实际效果EXPLAIN ANALYZE比单纯的EXPLAIN更有用。它不仅显示执行计划还会实际执行查询并返回每一步的耗时和行数。对于索引实验可以对比创建索引前后的EXPLAIN ANALYZE输出看Seq Scan和Index Scan的实际耗时差异。-- 创建索引前 EXPLAIN ANALYZE SELECT * FROM EMP WHERE DEPTNO 10; -- 创建索引 CREATE INDEX idx_emp_deptno ON EMP(DEPTNO); -- 创建索引后 EXPLAIN ANALYZE SELECT * FROM EMP WHERE DEPTNO 10;在数据量只有十几行的情况下两次输出的耗时可能都是零点几毫秒差异不明显。这时候可以手动插入更多测试数据比如用INSERT INTO EMP SELECT * FROM EMP;反复执行几次把数据量撑到几千行再对比索引效果。注意 EMP 有主键约束直接复制会因为 EMPNO 重复而失败需要修改 EMPNO 后再插入。对于事务并发实验EXPLAIN ANALYZE不能直接用于观察锁等待但可以配合pg_stat_activity视图查看当前活跃会话和等待事件。执行SELECT pid, state, wait_event_type, wait_event, query FROM pg_stat_activity WHERE state ! idle;可以看到哪些会话在等待锁以及等待的具体类型。还有一个实用技巧是在事务实验里用SAVEPOINT做部分回滚。比如在一个事务里插入多条数据如果中间某条失败可以用ROLLBACK TO SAVEPOINT回滚到之前的状态而不是整个事务回滚。这在验证事务的原子性时很有用。BEGIN; INSERT INTO DEPT VALUES (50, TEST, TESTCITY); SAVEPOINT sp1; INSERT INTO DEPT VALUES (10, DUP, DUP); -- 主键冲突失败 ROLLBACK TO SAVEPOINT sp1; -- 此时 DEPT 表里只有 DEPTNO50 的记录10 的插入被回滚 COMMIT;从那以后我每次做数据库实验都会先跑一遍EXPLAIN ANALYZE确认执行计划再开两个会话验证事务隔离最后用pg_stat_activity检查有没有残留的锁等待。这套流程走下来大部分玄学问题都能定位到具体原因。希望帮到你。本文还有配套的精品资源点击获取