☰
大连理工OpenGauss数据库上机实验:从建表到事务的完整避坑指南
2026/10/3 1:38:16 网站建设 项目流程

简介:这份资源是大连理工大学软件学院数据库系统课程的上机实验报告,基于华为 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。如果不套 NVL,SAL+COMM在 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 里出现了 JOB,OpenGauss 会报错提示 JOB 不在 GROUP BY 中。这一点比 MySQL 的宽松模式严格,从 MySQL 转过来的话需要特别注意。

3.4 多表查询与子查询的实现差异

多表查询部分覆盖了 JOIN、自连接、外连接。其中「查询 SCOTT 的上级领导的姓名」用的是自连接:FROM EMP, EMP LEADER WHERE EMP.ENAME='SCOTT' AND EMP.MGR=LEADER.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 DEPTNO=10;查看执行计划。如果索引生效,计划里会出现Index Scan或Bitmap Index Scan,而不是Seq Scan。

但这里有一个常见的误解:不是建了索引就一定会走索引。如果表的数据量很小(比如实验里的十几行数据),优化器可能判断全表扫描比索引扫描更快,依然选择 Seq Scan。这不是索引没建成功,而是优化器的成本估算结果。想验证索引确实可用,可以临时关闭顺序扫描:SET enable_seqscan = off;,再执行 EXPLAIN,这时应该能看到索引扫描的计划。

另一个坑是索引列上的隐式类型转换。如果 DEPTNO 是 INT 类型,查询写成WHERE DEPTNO='10',虽然 OpenGauss 会做隐式转换,但可能导致索引失效。常见做法是保持查询条件和列类型一致,INT 列就用 INT 值去查。

4.2 事务并发控制的隔离级别验证

事务并发控制实验是这份报告里最有实操价值的部分之一。报告通过设计并发场景,让两个会话同时操作同一张表,观察不同隔离级别下的行为差异。

OpenGauss 默认的隔离级别是 READ COMMITTED。在这个级别下,一个事务只能看到其他事务已经提交的修改。如果会话 A 更新了一行但还没提交,会话 B 查询这行时看到的还是旧值。报告里的实验通常会设计这样的场景:会话 A 执行BEGIN; UPDATE EMP SET SAL=9999 WHERE EMPNO=7369;但不提交,会话 B 执行SELECT SAL FROM EMP WHERE EMPNO=7369;,观察 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 OPTION,B 又把权限授予了 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 建在了其他 schema,EMP 的外键引用需要写成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, MDY,'1980-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 表里只有 DEPTNO=50 的记录,10 的插入被回滚 COMMIT;

从那以后我每次做数据库实验,都会先跑一遍EXPLAIN ANALYZE确认执行计划,再开两个会话验证事务隔离,最后用pg_stat_activity检查有没有残留的锁等待。这套流程走下来,大部分玄学问题都能定位到具体原因。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询