简介:郑州大学计算机与人工智能学院《数据库系统原理》课程实验报告,适合数据库初学者、正在完成同类实验的高校学生及复习备考者。报告完整记录了数据库原理实验的关键环节,重点展示万里数据库GreatSQL在CentOS虚拟机中的安装、配置与使用流程,包括关闭SELinux和防火墙的原因与命令、依赖包安装、二进制包下载解压、mysql系统用户及系统服务创建、启动与状态检查等,并配有进程、TCP连接、数据库文件等截图和命令说明。内容还覆盖数据库系统逻辑组件(基本表、视图、触发器、存储过程、约束)与物理组件的理解,以及实验报告撰写规范,如截图需带编号图题、表格需编号表名等,可帮助读者规范完成课程实验。压缩包仅含1个docx文档,大小3.69MB,结构紧凑便于阅读;已有509人学习,特别适合作为课程实验的环境搭建参考和排错思路借鉴。
1. 说到数据库原理实验报告,这套 GreatSQL 实验的练习远不止交差
数据库原理课程的实验报告,很多人的第一反应是“把教材上的 SQL 跑一遍,截图贴上去”。但这套实验包的价值比交差要大不少:它把环境配置、建库建表、索引管理、交互式查询和报告排版全部串成了一条可复现的路线。前三个实验从安装一个基于 MySQL 8.0 内核的国产数据库发行版起步,再到创建 STUDENTS、COURSES、SC 三张表,最后把 WHERE、IN、BETWEEN 这些查询条件逐条跑通;报告规范里还专门强调了截图编号、表格编号和书面语表达。对于正在赶实验报告、不想在命令行里来回翻车的同学,这套资料的命令可以直接抄;对于想脱离图形界面、通过终端理解数据库管理系统的初学者,它也是一份从零到查询的完整走查记录。
2. 环境搭建:为什么要关 SELinux,以及数据库二进制包的正确安装方式
2.1 安全策略的取舍:SELinux 和 firewalld 为什么必须处理
安装文档的一开始就出现了setenforce=0和systemctl stop firewalld两条命令。很多第一次接触 Linux 数据库部署的人会疑惑:数据库运行和安全策略冲突吗?答案是会冲突,尤其在 CentOS 7 这种默认开启 SELinux 的系统上。
SELinux 是强制访问控制机制,它给文件、进程、端口都打上了安全标签。数据库服务启动时要读取/etc/my.cnf、写入/var/lib/mysql、绑定 3306 端口,这些操作如果和 SELinux 的进程域不匹配,mysqld就可能出现权限拒绝。表现五花八门:有时候是启动后立即退出,有时候是能启动但无法创建 socket 文件。关闭 SELinux 不见得是最优解,但在教学环境里,它能把变量控制到最小,让后续实验的精力集中在 SQL 本身。防火墙同理——默认的 firewalld 区域不会放行 3306 端口,如果后面要使用图形化客户端做远程连接,端口不通会让人误判为服务没起来。
这里要特别提醒:setenforce=0只对当前运行状态生效,重启系统后 SELinux 会回到 enforcing。真正要永久关闭,必须修改/etc/selinux/config。实验报告里给的那条sed命令就是做这个的,它把配置文件中以SELINUX=开头的行整体替换成了SELINUX=disabled。
setenforce=0 sed -i '/^SELINUX=/c'SELINUX=disabled /etc/selinux/config systemctl disable firewalld systemctl stop firewalld命令说明:第一行立刻把 SELinux 切换到 permissive 模式,相当于临时放开限制;第二行持久化修改配置,防止重启后失效;第三行disable让 firewalld 不再开机自启;第四行stop立即关闭防火墙。实际工作中如果只是想临时测一下,只执行第一和第四行就够了,但在实验环境里两条都要写,否则下次开机又会被安全策略拦住。
2.2 依赖包清单与二进制解压:每个参数都在解决什么问题
在下载二进制包之前,先要解决依赖库。数据库发行版虽然自带大部分运行库,但 glibc 环境下的部分扩展库还需要系统提供。实验里给出的依赖安装命令是这样的:
yum install -y pkg-config perl libaio-devel numactl-devel numactl-libs net-tools openssl openssl-devel jemalloc jemalloc-devel perl-Data-Dumper perl-Digest-MD5 python2 perl-JSON perl-Test-Simple这里几个包值得单独说。libaio-devel是异步 I/O 的开发库,InnoDB 存储引擎的读写操作会用到它,缺失时可能导致初始化失败;jemalloc是内存分配器,官方二进制包推荐用它提升内存分配效率;numactl-devel和numactl-libs用于 NUMA 架构下的 CPU 内存绑定,多核服务器上对性能有影响;perl-Data-Dumper和perl-JSON是数据库自带运维脚本的运行依赖。如果是最小化安装的 CentOS,yum源里可能没有这些包,需要先执行yum install -y epel-release扩展软件源。
依赖装完后,进入下载和解压阶段。实验选择的是官方预编译二进制包,而不是源码编译。二进制包的好处是解压即用,不需要经历漫长的cmake和make过程,缺点是对系统环境敏感,所以前面那串依赖必须装齐。
cd /usr/local wget https://product.greatdb.com/GreatSQL-8.0.32-25/GreatSQL-8.0.32-25-Linux-glibc2.17-x86_64-minimal.tar.xz tar xf GreatSQL-8.0.32-25-Linux-glibc2.17-x86_64-minimal.tar.xz下载链接指向的是 8.0.32-25 版本的 minimal 最小化安装包。冠以 glibc2.17 表示它可以在 CentOS 7 的默认 glibc 版本下运行。tar xf不需要加-z,因为.xz解压由xz工具处理,CentOS 7 默认已经自带了。解压完成后,/usr/local/下会出现对应版本的目录,后面启动服务就是指向这个目录里的bin/mysqld。
接着是创建 mysql 系统用户和数据目录。数据库服务不允许直接以 root 身份运行,这是安全底线。mysql用户不分配登录 shell,只用于运行守护进程:
/sbin/groupadd mysql /sbin/useradd -g mysql mysql -d /dev/null -s /sbin/nologin mkdir /var/lib/mysql/ chown -R mysql:mysql /var/lib/mysql/ /var/log /usr/local/GreatSQL-8.0.32-25-Linux-glibc2.17-x86_64-minimalchown -R将数据目录、日志目录和安装目录都归属给 mysql 用户。如果漏掉这一步,mysqld启动时无法在/var/lib/mysql下创建系统表文件,会直接报 “Permission denied”。这类错误非常隐蔽,因为命令执行时本身不会提示。
2.3 服务注册到 systemd:启动、状态检查和常见失败定位
二进制包不会自动生成 systemd 服务文件,需要手动创建一个。这个文件把数据库进程交给 systemd 托管,之后就能用systemctl start/stop/status来管理,不需要每次手敲mysqld_safe。
vim /lib/systemd/system/greatsql.service服务文件的核心内容如下:
[Unit] Description=GDB Server After=network.target [Service] User=mysql Group=mysql ExecStart=/usr/local/GreatSQL-8.0.32-25-Linux-glibc2.17-x86_64-minimal/bin/mysqld LimitNOFILE=65535 [Install] WantedBy=multi-user.targetUser=mysql和Group=mysql指定进程以哪个身份运行,和前面创建的用户对应。After=network.target表示网络服务就绪后再启动数据库,避免监听端口时网络还没准备好。LimitNOFILE=65535把文件描述符上限调大,数据库并发连接高时会创建大量 socket 和文件句柄,默认 1024 的限制很容易触顶。
写完服务文件后必须执行systemctl daemon-reload,systemd 才会重新读取配置文件。之后启动数据库:
systemctl start greatsql systemctl status greatsql ps -ef | grep mysqld ss -lntp | grep mysqld ls /var/lib/mysqlsystemctl status能看到服务是否处于 active 状态、进程 PID 以及最近的日志输出。ps -ef | grep mysqld检查进程是否真实存在,如果只显示 grep 自身,说明服务启动后崩了。ss -lntp | grep mysqld确认 3306 端口在监听。最后列一下/var/lib/mysql数据目录,如果里面有mysql.ibd、ibdata1这些初始化文件,说明首次启动已经完成了系统库初始化。
启动之前还应该检查/etc/my.cnf是否存在。如果文件缺失,mysqld会使用编译时默认配置,数据目录可能指向/var/lib/mysql以外的位置。常见的做法是最少写上三段配置:
[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock port=3306 character-set-server=utf8mb4character-set-server=utf8mb4这一行非常关键。默认字符集在旧版本里可能是latin1,插入中文时命令行显示会变成乱码,排序规则也会受影响。实验环境里如果一开始没配,后面建表时每个字段都得手动指定字符集,非常麻烦。如果服务启动失败,第一件事是执行journalctl -u greatsql -n 50看日志,不要盲目重启。常见的失败原因就三类:数据目录权限不对、配置文件里的路径不存在、依赖库缺失。
3. 建库建表建索引:从逻辑设计到物理文件都要讲清楚
3.1 创建数据库 DB,以及数据页、表空间这些物理概念
实验二的第一步是创建一个名为 DB 的数据库。这个操作在原理上很简单,但它带出了一个值得深挖的问题:数据库文件到底存在哪里?
CREATE DATABASE DB; SHOW DATABASES; USE DB;在 MySQL 系列的存储引擎里,CREATE DATABASE做的事情是在数据目录下创建一个同名子目录。默认数据目录由/etc/my.cnf里的datadir指定,通常是/var/lib/mysql。执行完上面三条命令后,可以到系统里确认一下:
ls /var/lib/mysql | grep -i db如果能看到一个名为db的目录,说明数据库创建成功。这个目录在初始化时是空的,后续创建的表会以独立文件的形式出现在这里。InnoDB 引擎的表会同时生成.ibd文件存放数据行和索引,表结构则记录在数据字典中。
物理存储的单位是数据页,默认大小通常是 16384 字节。数据页是 InnoDB 从磁盘读取和写入的最小单位,一页里可能存放多行数据,也可能一个表有几百个页。理解数据页的意义在于:为什么查询走索引比全表扫描快,本质上就是减少了需要读取的数据页数量。实验报告里如果能写一句“数据以数据页存储,表对应一个或多个页文件”,这部分的得分点基本就拿到了。
3.2 STUDENTS、COURSES、SC 三张表:字段类型为什么这么选
建表的第一个版本非常简单,id int primary key这种结构适合快速演示,但不符合教材的语义需求。实验里从简版改成正式版,这一步是理解字段类型的好机会。
CREATE TABLE STUDENTS( Sno char(9) PRIMARY KEY, Sname char(10) NOT NULL, Sex char(2), Birthday DATE, Speciality char(20), Dno char(4) ); CREATE TABLE Courses( Cno char(5) PRIMARY KEY, Cname char(20) NOT NULL, Period int, Credit int ); CREATE TABLE SC( Sno char(9), Cno char(5), Grade int, PRIMARY KEY (Sno, Cno) );参数说明:学号 Sno 用char(9)而不是int,是因为学号是编号而非数值,它不需要参与加减乘除,而且用字符类型可以保留前导零,以后如果出现类似“001”的编号也不会丢失信息。Sname 加NOT NULL,姓名不应该为空。Sex 用char(2),教材里的取值只有“男”和“女”两个汉字。Birthday 用DATE类型,而不是字符串,这样后续可以使用日期函数做年龄计算。SC 表的主键是(Sno, Cno)复合主键,因为一个学生选一门课的成绩记录,需要学生和课程两个维度才能唯一确定。
在正式创建三张表之前,实验里先演示了删除操作:
DROP TABLE students CASCADE; DROP TABLE courses CASCADE; DROP TABLE sc CASCADE;实际上 MySQL 的DROP TABLE并不支持CASCADE关键字,这是 Oracle 数据库的写法。如果直接复制到命令行里执行,会报语法错误。正确写法是直接DROP TABLE 表名。这个问题也提醒了一件事:教材里的 SQL 不能无脑照抄,需要根据具体的数据库产品调整。
填充数据是建表后的必做步骤。这里要注意插入顺序——先插入父表,再插入子表:
INSERT INTO STUDENTS VALUES ('201705001', '张华', '男', '1999-12-16', '计算机', 'CS'), ('201705002', '张晓霞', '女', '1999-01-01', '软件工程', 'SE'), ('201706001', '李玉', '女', '1999-10-17', '数学', 'MA'); INSERT INTO COURSES VALUES ('CS101', '计算机科学导论', 2, 1), ('CS102', '数据库系统', 4, 4), ('CS103', '操作系统', 4, 3); INSERT INTO SC VALUES ('201705001', 'CS101', 89), ('201705002', 'CS102', 92), ('201705003', 'CS101', 88);这段插入脚本隐藏着一个坑。SC 表里出现了201705003,但 STUDENTS 表的插入数据里只有201705001、201705002和201706001,没有201705003。如果在创建 SC 表时同时定义了外键约束,这条插入就会直接违反外键约束,返回Cannot add or update a child row。教材数据里这种不一致很常见,实验时要么把 STUDENTS 表补上对应学号,要么去掉外键约束再插入。这个细节后面避坑章节还会展开。
3.3 索引:普通索引、唯一索引与删除操作
索引是数据库原理实验的必考内容。实验要求在 Students 表的 Dno 字段上创建普通索引,在 Courses 表的 Cno 字段上创建唯一索引:
CREATE INDEX Student_Dept ON Students (Dno); CREATE UNIQUE INDEX Course_Cno ON Courses (Cno);普通索引声明的语义是“允许重复值”,它只加速查询,不约束数据。Dno 是院系编号,一个系有多个学生,所以这里用普通索引合适。唯一索引的语义是“字段值不能重复”,Cno 本来就是主键,主键本身已经隐含唯一索引,这里再创建一个唯一索引属于锦上添花,主要演示UNIQUE关键字。
删除索引的语法和 Oracle 不同,MySQL 使用DROP INDEX 索引名 ON 表名:
DROP INDEX Course_Cno ON Courses;删除后可以再次执行SHOW INDEX FROM Courses;验证索引是否移除。这里有一个设计上的问题值得思考:主键和唯一索引都能保证唯一性,为什么还需要主键?主键是表的逻辑标识,每行数据必须有且只有一个;唯一索引只是业务约束,不一定是行的身份标识。索引不是越多越好,每个索引在加速查询的同时都会拖慢插入和更新操作,因为写入数据时,所有索引都需要同步维护。这种“读写权衡”的观念,实验报告的分析部分可以写进去。
最后处理表间约束。SC 表中的 Sno、Cno 分别引用 Students 和 Courses,正确做法是在建表后用ALTER TABLE补上外键:
ALTER TABLE SC ADD CONSTRAINT fk_sc_sno FOREIGN KEY (Sno) REFERENCES Students(Sno); ALTER TABLE SC ADD CONSTRAINT fk_sc_cno FOREIGN KEY (Cno) REFERENCES Courses(Cno);但这里有个执行前提:SC 表里的 Sno 和 Cno 值,在父表里必须已经存在。如果上面插入的 SC 数据里有201705003,而这个学号在 Students 表里不存在,这条ALTER TABLE会直接失败。所以遇到外键约束添加失败,不要怀疑语法,先查数据完整性。
4. 交互式 SQL 查询:把教材上的 WHERE、IN、BETWEEN 跑成自己的数据
4.1 全部查询与计算字段:不带 WHERE 的简单场景
实验三的第一步是先建好完整的数据表,再开始各种查询。不带 WHERE 的查询最简单,但里面也藏着一个小知识点——计算字段。
SELECT * FROM Courses; SELECT Sname, 2024 - YEAR(Birthday) AS Age FROM Students;第一条查询返回 Courses 表的全部列,没有任何过滤条件,执行计划里走的是全表扫描。第二条查询里YEAR(Birthday)从日期字段中提取出生年份,然后用当前年份减去它得到年龄。这里有个隐患:如果系统当前时间不是 2024 年,算出来的年龄就是错的。更严谨的做法是用TIMESTAMPDIFF(YEAR, Birthday, CURDATE()),它会自动根据当前时间计算精确年龄。
SELECT Sname, TIMESTAMPDIFF(YEAR, Birthday, CURDATE()) AS Age FROM Students;TIMESTAMPDIFF是 MySQL 系列的日期差函数,第一个参数指定单位,第二个参数是起始日期,第三个参数是结束日期。这个函数的好处是不用硬编码年份,每年的结果都正确。实验报告里如果写“计算字段可以用 YEAR 函数,也可以用 TIMESTAMPDIFF 更严谨”,体现的思路会让报告加分。
4.2 条件查询:等于、小于、范围与 NOT 的组合
带 WHERE 的查询是交互式 SQL 的核心。实验里要求按教材完成多个查询实例,这里挑几个典型写法拆解:
SELECT Tname, Sex FROM Teachers WHERE Title = '讲师'; SELECT DISTINCT Sno FROM SC WHERE Grade < 60; SELECT Sname, Speciality FROM Students WHERE YEAR(Birthday) BETWEEN 1997 AND 1999; SELECT Sname, Speciality FROM Students WHERE YEAR(Birthday) NOT BETWEEN 1997 AND 1999;第一条WHERE Title = '讲师'用了字符串等值比较。中文引号会导致语法错误,这里必须用 ASCII 单引号。第二个查询用DISTINCT去重,查出所有考试不及格的学生学号;如果不加DISTINCT,同一个学号挂科多门课会出现多行。对于实践报告,提醒"不及格课程多门时用DISTINCT"很实用。
第三和第四条是范围查询。BETWEEN 1997 AND 1999是闭区间,等价于>= 1997 AND <= 1999;NOT BETWEEN取反。这个写法比手写两个比较条件更清晰。注意YEAR(Birthday)函数把日期转成年份再做比较,这是对字段套函数,如果 Birthday 列上有索引,套函数会导致索引失效,全表扫描。数据量小无所谓,但如果到了几十万行的场景,应该改成Birthday >= '1997-01-01' AND Birthday < '2000-01-01',让索引能发挥作用。
接下来是 IN 条件:
SELECT Sno, Sname FROM Students WHERE Speciality IN ('计算机', '软件工程'); SELECT Sno, Sname FROM Students WHERE Speciality NOT IN ('计算机', '软件工程');IN 列表本质是多个 OR 条件的简写。NOT IN需要注意 null 陷阱——如果被比较列里有 NULL 值,NOT IN的结果可能不符合直觉,它会对 NULL 返回 UNKNOWN,导致整行被过滤掉。在实验数据较少时不容易暴露,但在真实数据里很容易让人困惑。
4.3 更新操作:插入、修改、删除时容易被忽略的细节
教材的更新操作分三类:插入、修改、删除。实验报告要求关注标准 SQL 和 M 系数据库 SQL 的差异。这里有一个最重要的差异,就是前面提到的DROP TABLE ... CASCADE,标准 SQL 支持,而 M 系数据库不支持。
UPDATE Students SET Sname = '张华' WHERE Sno = '201705001'; DELETE FROM SC WHERE Grade < 60; INSERT INTO Students(Sno, Sname, Sex, Birthday, Speciality, Dno) VALUES ('201705004', '王芳', '女', '2000-03-15', '计算机', 'CS');UPDATE 语句必须带上 WHERE,否则会更新整张表的全部行,这是最容易翻车的操作。经验做法是先写 WHERE,再回头补 SET,确保过滤条件明确。DELETE 同理,不带 WHERE 会清空整张表,而且这个操作不可回滚——除非事务里已经执行了START TRANSACTION。插入语句列出字段名比不列字段名更清晰,也更能避免字段顺序变化导致的数据错位。
5. 实验过程中的避坑清单:五个真实报错与对应解法
5.1 现象:服务启动失败且日志提示权限不足
执行systemctl start greatsql后,服务一直是 failed 状态。journalctl -u greatsql -n 50里出现Permission denied或Can't open file /var/lib/mysql/ibdata1。
原因:只执行了setenforce=0,没有在/etc/selinux/config里把 SELINUX 改为 disabled。第一次启动时 SELinux 被临时关闭,一切正常;重启系统后 SELinux 恢复 enforcing,数据目录写入被安全策略拦截。另一个可能原因是mysql用户没有数据目录的写权限,因为chown -R那步被跳过了。
解决:检查/etc/selinux/config是否真的是SELINUX=disabled,检查/var/lib/mysql的属主是否为 mysql:mysql。两个条件都满足后再systemctl start greatsql。从那以后我每次装数据库都会先跑一次getenforce确认状态,而不是想当然以为关过了。
5.2 现象:日期数据插入后查询不到结果
插入 Birthday 字段时用了1999_12_16这种下划线分隔的格式,INSERT 成功了,但WHERE Birthday = '1999-12-16'查不到。
原因:M 系列数据库默认启用了严格模式,日期字符串必须符合YYYY-MM-DD格式。下划线格式在某些版本里会被解析为 0 值日期,或者直接报Incorrect date value错误。这条记录很可能根本没插入,或者被存成了全零日期。
解决:统一使用1999-12-16这种标准格式。如果表里已经混入了全零日期,先执行SELECT * FROM Students WHERE Birthday = '0000-00-00'定位数据,再手动修复或删除。
5.3 现象:学号字段用了 int 导致输出不符合预期
建表时把 Sno 定义成int,插入201705001后查询结果变成了201705001,看起来没问题,但如果学号是0001这种前导零格式,存储后直接丢失了 0,变成 1。
原因:int 是数值类型,它只保存数值大小,不保存格式。前导零不是数值的一部分,所以必丢。学号、身份证号、电话号码这类“看起来像数字”但不需要计算的字段,都应该用 char 或 varchar。
解决:把 Sno 改成char(9),已经建表的执行ALTER TABLE STUDENTS MODIFY Sno char(9);。这个坑在实验二里就会遇到,建议建表时直接按正式版结构来,不要偷懒用 int 简化。
5.4 现象:SQL 一直报语法错误但看起来没问题
复制教材或 Word 文档里的 SQL 执行,返回1064 - You have an error in your SQL syntax,检查半天发现引号用的是中文全角'和'。
原因:很多教材 PDF 或网页里的引号会被排版软件自动转成全角。数据库只认 ASCII 单引号,全角引号直接导致语法解析失败。
解决:写 SQL 时统一在终端或编辑器中输入,不要直接复制带格式的文档内容。如果已经报错,把引号替换成英文单引号。这个坑非常隐蔽,新手排查语法问题往往在括号和关键词上折腾半天,实际只是引号字符的问题。
5.5 现象:外键约束阻止数据插入或 ALTER TABLE 失败
插入 SC 表的201705003时,报错Cannot add or update a child row: a foreign key constraint fails。或在执行ALTER TABLE SC ADD FOREIGN KEY...时卡住。
原因:SC 表的 Sno 字段里存在父表 Students 中没有的学号。外键约束要求子表的所有引用值都必须在父表主键中存在,否则约束无法通过验证。
解决:先检查不一致数据,执行SELECT SC.Sno FROM SC LEFT JOIN Students ON SC.Sno = Students.Sno WHERE Students.Sno IS NULL;,找出孤立记录后,要么在父表补插入对应学生,要么删除这些子表记录,然后重新执行 ALTER TABLE。这个顺序问题是数据库设计里的经典场景,所有外键约束的添加顺序都应该是先父后子。
6. 把实验过程变成一份规范报告:编号、截图和表题的固定套路
6.1 截图下方必须有图编号和图题
实验报告的要求里反复出现一条:截图下方需要有图的编号和图题。这听起来很简单,但很多人交上去的报告里只有“图1”“结果如下”这种敷衍写法。规范的格式是“图1 创建数据库结果”,图编号用阿拉伯数字按顺序排,图题应该概括这张图表的内容,而不是写“运行截图”。截图要裁剪干净,只保留和当前操作相关的窗口区域,不要整张桌面糊上去。
6.2 表格上方那行“表1 学生表”不是可选项
报告里如果有数据展示,比如 SC 表的部分数据,应该在表格上方写“表1 选课成绩表”,下面才是属性行和数据行。属性行就是列名 Sno、Cno、Grade,数据行是具体记录。表格编号和图编号分开计,不要混用“图 1”“表 1”的时候编号串在一起。这个格式要求看似是排版细节,数据库课程设计里往往是很容易被扣分的一项。
6.3 报告正文里的“过程→问题→更正→分析”顺序
写实验报告最容易犯的毛病是只贴一段 SQL 加一张截图,完全没有过程描述。规范的做法是:先用一到两句话说明本步要做什么,再贴出核心命令,然后写执行过程中遇到的问题、如何排查和更正,最后写出结果并分析原因。比如在安装数据库的环节,报告里可以写“关闭 SELinux 和防火墙是为了避免安全策略限制数据库端口监听”,而不是光写两行命令。
我的习惯是每次实验过程中开一个临时目录,把每一步的关键报错截图和纠正命令单独存下来,写报告时照着这个素材库整理。这样既不会漏掉过程,也不会在截图编号上翻车。希望帮到你。
本文还有配套的精品资源,点击获取