简介:ZZU郑州大学计算机与人工智能学院《数据库系统原理》实验报告完整版(2023-2024第二学期),适用于该校计算机类本科生及需要完成同类数据库实验的读者。报告以实验一“认识DBMS系统”为主线,系统梳理了商用数据库管理系统(万里数据库GreatSQL)的安装、启动、停止等操作,并详细记录关闭SELinux与防火墙的原因和命令、安装依赖包、下载解压二进制包、配置systemd服务、创建mysql用户及启动服务等完整流程。每个环节均配有命令、截图编号和结果说明,同时对数据库系统的逻辑组件(基本表、视图、触发器、存储过程、约束)与物理存储方式作了归纳,便于理解数据库底层运行机制。整份资源仅1个docx文档,压缩包大小3.69MB,内容结构清晰,从实验目的、实验类型、学时分配到操作步骤和结果分析一应俱全。已有509人学习,可作为课程实验报告撰写、数据库环境搭建和排错思路的重要参考。
1. 认识DBMS:这份数据库实验报告值不值得照着做
数据库原理这门课,理论听懂了不算数,真到虚拟机里把DBMS装起来、建库建表、插数据、写查询,才算把教科书里的概念落到手上。这份实验报告来自某高校计算机专业的《数据库系统原理》课程,核心是用GreatSQL把三个实验完整走了一遍:认识DBMS、创建数据库表和索引、交互式SQL查询。最大的看点是它把安装、配置、启动、建表、查询从零到一记录得很完整——对于正在做同款实验的学生,可以直接对照步骤抄作业、补截图、填问题总结;对于想快速搭一套MySQL系数据库环境的开发者,这里也排掉了一大半安装阶段的坑。报告本身是课程作业,但技术流程是通用的,这才有拆的价值。
2. GreatSQL安装与启动:二进制包部署和服务管理的完整链路
实验一的实质任务是把GreatSQL装进CentOS7虚拟机,再用systemctl把它管起来。装MySQL系数据库通常有四条路:yum/rpm包、docker镜像、源码编译、二进制解压。这份报告选的是二进制minimal包,理由很简单——官方编译好的,解压即用,不碰编译期依赖;minimal裁剪了调试组件,虚拟机里跑更轻。用yum装的社区版MySQL版本往往比较旧,实验里要验证8.0.32的并行查询和MGR特性就体验不到了。所以选型上,二进制包是教学环境里最稳的。
2.1 环境准备:关闭SELinux和防火墙,到底关的是什么
实验第一步先把系统的两道安全闸门给关了:
setenforce 0 sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config systemctl stop firewalld systemctl disable firewalldsetenforce 0是临时把SELinux切到permissive,立即生效;sed那一行把配置文件里的SELINUX=enforcing改成disabled,重启后仍然生效。为什么要关SELinux?因为mysqld进程要写/var/lib/mysql、要监听3306端口、要读写socket文件,这三类操作都在SELinux的策略管辖范围内,策略没配好就会看到数据库启动失败或者连不上,日志里只有一句Permission denied,非常难排查。防火墙同理,3306被挡,客户端就连不上。
需要说清楚:这个“关”是教学环境的省事做法。生产环境里正确姿势是配置SELinux布尔值和firewalld放行3306/tcp,而不是一刀切。报告里那句“关闭可以简化配置、提高性能”其实只说对了一半,严格的SELinux策略确实有性能开销,但虚拟机里做实验,差别基本可以忽略。
2.2 依赖包安装:这串包名不是白给的
依赖包用一条yum命令装齐:
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对应InnoDB的异步IO,没有它在高并发下会报io_setup失败;numactl系列是NUMA内存分配策略工具,多路物理机必备,虚拟机里装了也不亏;jemalloc是官方推荐的内存分配器,替换glibc的malloc,能减少内存碎片,对长期运行的数据库进程很关键;openssl-devel是SSL/TLS通信的库,MySQL 8.0默认开启加密连接,缺了连配置都过不去。perl和python2主要是给一些管理脚本用的。
正常装完不会有输出告警。如果看到“No package python2 available”,不用慌,CentOS 7自带python2,注意别和编译类报错搞混。这里照抄报告的命令就行,顺序不要乱。
2.3 解压二进制包与systemd服务文件
进入安装目录,拉包、解压:
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是8.0系列里的稳定迭代,glibc2.17说明它适配CentOS7的系统库。解压后目录名带完整版本字符串,后续basedir就指向它,最好别改名字,免得和文档对不上。我一般不会把目录重命名成mysql,保持版本号在路径里,升级时一目了然。
接下来要把GreatSQL注册成systemd服务,手工创建/lib/systemd/system/greatsql.service。按最小化配置,内容可以写成这样:
[Unit] Description=GreatSQL Server After=network.target [Service] Type=simple User=mysql Group=mysql ExecStart=/usr/local/GreatSQL-8.0.32-25-Linux-glibc2.17-x86_64-minimal/bin/mysqld --defaults-file=/etc/my.cnf LimitNOFILE=65535 Restart=on-failure [Install] WantedBy=multi-user.targetType=simple告诉systemd,ExecStart启动的这个mysqld进程就是服务本体;User和Group指定用mysql账号运行,避免用root跑数据库;LimitNOFILE调高文件描述符上限,省得高并发下报Too many open files。Restart=on-failure表示非正常退出时自动拉起,实验里偶尔写错配置导致mysqld崩溃,它能自动救回来。写完执行systemctl daemon-reload,让systemd重新读取单元文件。
2.4 创建mysql用户、目录和my.cnf
数据库进程不能跑在root下,所以要建一个不能登录的系统账号:
/sbin/groupadd mysql /sbin/useradd -g mysql mysql -d /dev/null -s /sbin/nologin mkdir -p /var/lib/mysql /var/log/mysql /var/run/mysqld chown -R mysql:mysql /var/lib/mysql /var/log/mysql /var/run/mysqlduseradd的-d /dev/null -s /sbin/nologin是故意把家目录和shell都禁掉,这样mysql用户只能作为服务账号存在,即使进程被攻破也拿不到交互shell。数据目录/var/lib/mysql、错误日志目录/var/log/mysql、socket文件目录/var/run/mysqld这三个目录都必须提前建好,并且属主改成mysql:mysql,否则启动时连目录都写不进去。
然后是全局配置/etc/my.cnf。实验报告里只说“编辑”,没说内容,这里按最小化配置补一个能跑的版本:
[mysqld] basedir=/usr/local/GreatSQL-8.0.32-25-Linux-glibc2.17-x86_64-minimal datadir=/var/lib/mysql socket=/tmp/mysql.sock port=3306 user=mysql log-error=/var/log/mysql/mysql.log pid-file=/var/run/mysqld/mysqld.pidbasedir和datadir必须和实际路径完全一致;socket选/tmp/mysql.sock是兼容大多数客户端工具的默认值;pid-file指向/var/run/mysqld,这个目录上面已经建好。如果以后要上MGR,还要再配server_id、gtid_mode这些参数,实验阶段先不用。
2.5 初始化数据目录:安装中最容易漏的一步
实验报告里从解压直接跳到启动,漏掉了二进制安装最关键的初始化步骤。二进制包解压出来,/var/lib/mysql是空的,mysqld第一次启动会直接报错退出,错误日志里写着“Data directory not initialized, you can use --initialize”。我一般会先做一次无密码初始化:
cd /usr/local/GreatSQL-8.0.32-25-Linux-glibc2.17-x86_64-minimal bin/mysqld --defaults-file=/etc/my.cnf --initialize-insecure --user=mysql--initialize-insecure会生成一个root空密码账号,适合实验环境,登录时不用去翻日志找随机密码;生产上则用--initialize,它会生成随机密码并写进错误日志。初始化完成后,/var/lib/mysql下会出现ibdata1、mysql.ibd、performance_schema等系统表空间文件。这一步如果报缺少共享库,回2.2补依赖包就行。
2.6 启动、验证与日常管理
初始化到位后,用systemd启动并确认状态:
systemctl daemon-reload systemctl start greatsql systemctl status greatsql ps -ef | grep mysqld ss -lntp | grep 3306 ls -l /var/lib/mysqlsystemctl status输出active (running)基本就算过了,但我会习惯再补两刀:ps确认mysqld进程确实存在且是mysql用户;ss -lntp看到3306在监听才说明网络层通了。最后ls一下数据目录,看到ibdata1和mysql目录心里才踏实。
日常管理就是三件套:systemctl start greatsql、systemctl stop greatsql、systemctl restart greatsql。stop时会等待事务落盘,慢是正常的,不要反复点start。如果启动失败,优先看错误日志尾部:tail -50 /var/log/mysql/mysql.log,八成能找到SELinux权限或初始化遗漏的线索。这套流程走下来,后面实验二、三才有稳定的运行环境。
3. 数据库的逻辑组件与物理存储:用SQL把黑匣子看清
实验一的后半段是概念验证:把数据库拆成逻辑组件和物理组件,逐一对号入座。逻辑组件是用户在SQL层能直接操作的对象,物理组件则是磁盘上真实的文件结构。这一章的核心思路就一个——概念不要背,要用SQL去information_schema里查一遍。
3.1 逻辑组件:五类对象的定义与查看命令
数据库的逻辑组件包括基本表、视图、触发器、存储过程和约束。它们都是“意义上存在”的对象,真正的落脚点是数据字典。
基本表是真实存储数据的结构,一个逻辑表对应磁盘上一个或多个文件。视图是虚拟表,它不存数据,本质是一条被保存下来的查询语句,查询视图时动态执行底层SQL。触发器是绑定在表上的事件钩子,INSERT、UPDATE、DELETE发生时自动触发。存储过程是预编译的SQL语句集合,适合封装复杂业务逻辑,减少客户端和服务器间的往返。约束是数据完整性的规则,包括主键、外键、唯一、非空、CHECK等。
查看这些组件,最直接的办法是查询information_schema,它是数据库自带的数据字典:
SHOW TABLES; SELECT TABLE_NAME FROM information_schema.VIEWS WHERE TABLE_SCHEMA='DB'; SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA='DB'; SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_TYPE='PROCEDURE' AND ROUTINE_SCHEMA='DB'; SELECT TABLE_NAME, CONSTRAINT_NAME, CONSTRAINT_TYPE FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='DB';其中VIEWS表存视图定义,TRIGGERS表存触发器的触发事件和关联表,ROUTINES表存存储过程,TABLE_CONSTRAINTS表把每个约束的类型都列出来。原报告里查看视图用的是SHOW FULL PROCESSLIST,那是在看连接进程,和视图没关系。更严谨的写法是上面第一条的SELECT,拆这份报告时我特意把这处圈了出来,这类“命令张冠李戴”在课程作业里很常见,正好说明对照数据字典查组件比截图更有说服力。
3.2 物理组件:数据页、表空间和日志文件
物理组件的核心是数据页。InnoDB的整个存储体系建立在页之上,默认每页16KB,表数据以B+树形式组织在这些页里。页再往上,是一系列文件:每张表独立的.ibd文件(开启innodb_file_per_table时)、系统表空间ibdata1、redo日志文件、undo日志文件,以及各种元数据文件。
用系统变量把物理结构验证一遍:
SHOW VARIABLES LIKE 'innodb_page_size'; SHOW VARIABLES LIKE 'innodb_data_file_path'; SHOW VARIABLES LIKE 'innodb_file_per_table'; SHOW VARIABLES LIKE 'innodb_log_files_in_group';innodb_page_size返回16384就是16KB;innodb_file_per_table为ON,说明每张表一个.ibd;innodb_data_file_path指向ibdata1的路径和初始大小;redo日志默认两组,对应#innodb_redo目录。数据存放位置怎么确定?路径由datadir变量决定,实验配置里写的是/var/lib/mysql。用ls -lh /var/lib/mysql看一眼,ibdata1和以库名命名的子目录都在明面上。物理层面的认识对后续性能排查很有用——一张表查得快慢,本质上是在B+树索引页上做的二分查找次数;页分裂多、碎片多,查询自然慢。实验报告不需要写太深,但能说出“存储单位是数据页,默认16KB,一个页可能包含多行记录”,已经比背概念强很多。
3.3 GreatSQL特性速览:为什么课程选它而不是社区版MySQL
实验一里专门列了GreatSQL的几个工具特性,这不是凑字数,而是这版数据库和社区版MySQL的差异化卖点。MGR(Group Replication)提供了高可用方案,GreatSQL在此基础上加了地理标签,解决多机房数据同步;改进了流控算法,大事务能及时检测出来,避免MGR集群被一个超大事务拖死。InnoDB并行查询针对OLAP场景优化,据官方测试TPC-H性能平均提升15倍,最高40多倍,做实验跑复杂统计查询时感觉最明显。事务锁从红黑树改成无锁哈希结构,高并发写入时锁竞争更小。仲裁节点则是在不增加数据节点的情况下提升可用性,降低服务器成本。
课程选它而不是社区版MySQL,教学上有个明显好处:学生既能接触标准SQL语法,又能观察到商用增强特性,比如单主模式的快速选主、流控机制,这些都在社区版里见不到。实验环境虽然用不起多节点集群,但至少能在文档和初始化脚本里看到这些增强点的存在。对学习者来说,重点还是先把标准SQL练熟,MGR、并行查询这类特性作为“以后做DBA”的方向了解即可。
3.4 一次性体检:把组件信息导出成报告素材
做实验报告时,最烦的是截图不齐、分析没依据。这里可以写一条万能体检语句,把当前库里的对象一次清点完:
SELECT TABLE_NAME, TABLE_TYPE, ENGINE, TABLE_ROWS, DATA_LENGTH FROM information_schema.TABLES WHERE TABLE_SCHEMA='DB';TABLE_TYPE区分BASE TABLE和VIEW;ENGINE列出每张表的存储引擎;TABLE_ROWS是估算行数,不是精确值;DATA_LENGTH是数据占用字节数。把这条查询结果放进实验报告的“结果分析”,再配一条SHOW VARIABLES LIKE 'innodb_page_size',物理存储和逻辑对象两块就都有实锤了。如果想把输出存下来慢慢看,还可以用mysql客户端重定向:mysql -uroot -p DB -e "查询语句" > component_report.txt。这章的任务就是把“看数据库”这个动作从黑匣子变成可重复执行的SQL。
4. 创建数据库、表和索引:DDL的规范与三个常见错
实验二开始动手建东西。这一章的SQL都不难,难在规范性和几个一不留神就翻车的语法细节。我会按照“建库→建表→填数据→建索引→删表”的顺序拆,同时把常见误用单独标出来。
4.1 创建数据库并确认存储位置
第一条SQL是最简单的,但很多人建完库就忘了看它落在哪:
CREATE DATABASE DB; SHOW DATABASES; USE DB;CREATE DATABASE创建的是逻辑库。在InnoDB体系下,对应datadir下会新建一个名为DB的目录,后续表文件都放里面。要确认存储位置,可以用系统命令看物理路径:
ls -ld /var/lib/mysql/DB数据库文件个数和大小,建表前可以先查information_schema.tables确认库存在,建表后再通过SHOW TABLE STATUS看到每个表的数据大小和行数。实验报告要求“观察数据库文件个数、大小”,其实就是这几条命令的输出。顺手执行SHOW CREATE DATABASE DB;还能看到库的默认字符集,比如utf8mb4,这对中文数据的写入很重要。
4.2 基本表结构:定长字符串、主键与NOT NULL
实验二先建了三张教学表:
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) );上面每张表都定义了主键。STUDENTS单列主键Sno,SC用复合主键(Sno,Cno),表示“一个学生的一门课只有一条成绩”,这是业务规则的硬约束。Sname和Cname加NOT NULL,姓名和课程名不允许空值。使用char而不是varchar,是因为学号、课程号这种长度固定且经常作为连接条件的列,定长字符串比较稳定,索引也更紧凑;姓名虽然长度会变,但教学表里统一定长能减少偏移计算,真实系统一般用varchar。
三个常见错:第一,实验报告初稿里出现过全角逗号“id int,cid int”,在SQL里只有半角逗号合法,从中文Word复制粘贴代码时最容易带进来;第二,Sno用char(9)还是char(12),必须和后续所有相关表保持一致,否则加外键时直接报错;第三,Birthday列必须用DATE类型,如果图省事存成字符串,后面YEAR()函数和BETWEEN日期比较都会出问题。
建表之后用SHOW TABLE STATUS验证一下:
SHOW TABLE STATUS LIKE 'STUDENTS';重点看Engine列是不是InnoDB,Rows列的行数,Data_length列的数据字节数。这些数字可以直接写进实验报告的“结果分析”。
4.3 批量填充数据:INSERT多行的写法
表建好,用多行VALUES一次性填充:
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);要点有两个。一是VALUES后面用逗号分隔多个元组,一条语句插多行,比逐条INSERT快很多;二是日期必须写成'1999-12-16'这种ISO格式,用下划线或斜杠都可能被MySQL转成0值日期。如果表里部分列允许空值,建议写成带列名的形式:INSERT INTO STUDENTS(Sno, Sname, Sex) VALUES (...),字段顺序变化时不容易张冠李戴。插完后跑SELECT * FROM STUDENTS确认行数和内容,这一步别省。
4.4 索引的创建和删除:语法差异与选型
实验二要求在Dno上建普通索引,在Cno上建唯一索引:
CREATE INDEX Student_Dept ON Students(Dno); CREATE UNIQUE INDEX Course_Cno ON Courses(Cno); DROP INDEX Course_Cno ON Courses;Dno是专业方向的查询条件,建索引能减少全表扫描;Cno是课程主键,本身已有主键索引,这里再建UNIQUE INDEX是演示唯一性约束的用法。删除索引在MySQL里的语法是DROP INDEX 索引名 ON 表名,注意和SQL Server的DROP INDEX 表名.索引名正好相反,这个差异在跨数据库迁移时特别容易踩。索引不是越多越好,写操作多的表索引太密反而拖慢INSERT和UPDATE,实验里建这两个就够演示了。
4.5 删除表时cascade的误区
实验二初稿里写的是drop table students cascade;,但这是Oracle/SQL Server的遗产语法,MySQL系根本不支持,直接执行会报语法错误。MySQL删除表就是:
DROP TABLE STUDENTS; DROP TABLE Courses; DROP TABLE SC;有外键关系时,要先删子表(SC),再删父表(STUDENTS、Courses)。如果顺序反了,MySQL会报“cannot drop table referenced by a foreign key constraint”。实验二阶段三张表还没有外键,随便删,到实验三有了外键,这个顺序就非常重要了。这里正好埋一个伏笔,留到第5章排查部分展开。
5. 交互式SQL实战与常见问题排查:数据填充、查询与五个坑
实验三把难度提了一档:先建六张有关联的表,再填充数据,最后跑各种查询和更新。这一章我会先给完整的表结构和外键关系,再走数据填充,最后把查询套路过一遍——真正的重头戏是5.4里的五条踩坑实录。
5.1 六张表和它们的外键关系
实验三的数据模型是一个简化版教务系统:departments院系、teachers教师、students学生、courses课程、sc选课、teaches授课。先建四张基础表:
CREATE TABLE Departments( Dno char(4) PRIMARY KEY, Dname char(20), Dheadno char(7) ); CREATE TABLE Teachers( Tno char(7) PRIMARY KEY, Tname char(10) NOT NULL, Sex char(2) CHECK(Sex IN ('男', '女')), Birthday DATE, Title char(10), Dno char(4) ); CREATE TABLE Students( Sno char(12) PRIMARY KEY, Sname char(20) NOT NULL, Birthday DATE, Speciality char(20), Dno char(4), Sex char(4) ); CREATE TABLE Courses( Cno char(5) PRIMARY KEY, Cname char(30) NOT NULL, Period SMALLINT, Credit SMALLINT );SC和Teaches是关联表,各自带外键。原报告里Teaches一开始没写外键,后面用ALTER补,这里我保持同样的流程,先建一个暂时没有外键的版本:
CREATE TABLE SC( Sno char(12), Cno char(5), Grade int CHECK(Grade > 0 AND Grade <= 100), PRIMARY KEY (Sno, Cno), FOREIGN KEY (Sno) REFERENCES Students(Sno), FOREIGN KEY (Cno) REFERENCES Courses(Cno) ); CREATE TABLE Teaches( Tno char(7), Cno char(5), TCsores int, PRIMARY KEY (Tno, Cno) );TCsores这列名原报告就这么写的,明显是TCscore的笔误,不影响实验。随后用ALTER补齐Teaches和另外两张表的外键:
ALTER TABLE Teaches ADD FOREIGN KEY (Cno) REFERENCES Courses(Cno); ALTER TABLE Teaches ADD FOREIGN KEY (Tno) REFERENCES teachers(Tno); ALTER TABLE Students ADD FOREIGN KEY (Dno) REFERENCES Departments(Dno); ALTER TABLE Teachers ADD FOREIGN KEY (Dno) REFERENCES Departments(Dno);外键关系如果画成表,就是下面这样:
| 外键列 | 所属子表 | 引用父表 |
|---|---|---|
| SC.Sno | SC | Students |
| SC.Cno | SC | Courses |
| Teaches.Tno | Teaches | Teachers |
| Teaches.Cno | Teaches | Courses |
| Students.Dno | Students | Departments |
| Teachers.Dno | Teachers | Departments |
需要特别注意,MySQL对参照列的类型要求非常严格:子表和父表的列数据类型必须完全一致,比如SC.Sno和Students.Sno都得是char(12),char(10)配char(12)都会报1215错误。CHECK约束在8.0.16之后才真正生效,GreatSQL 8.0.32没问题;如果换到老版本MySQL,这个CHECK会被静默忽略,这是版本差异,不是SQL写错。
5.2 插入数据:先父后子,日期格式别偷懒
有外键之后,插入顺序是硬约束:先父表,后子表。所以正确顺序是departments → teachers/students → courses → sc/teaches。父表数据不存在时,子表插入会直接触发1452外键约束失败。贴一段示范:
INSERT INTO departments VALUES ('CS', '计算机系', 05001), ('MA', '数学系', 06001), ('PH', '物理系', 07001), ('CH', '化学系', 08001); INSERT INTO teachers VALUES (05001, '张海峰', '男', '1969-02-21', '教授', 'CS'), (05002, '刘晓明', '男', '1978-10-06', '副教授', 'CS'); INSERT INTO students VALUES (201705001, '张华', '1999-12-16', '计算机', 'CS', '男'), (201705002, '李玉', '1998-10-17', '计算机', 'CS', '女'); INSERT INTO courses VALUES ('cs101', '计算机科学导论', 2, 1), ('cs102', 'C语言', 4, 3); INSERT INTO sc VALUES (201705001, 'cs101', 89), (201705002, 'cs101', 91);插入时最常见的错误就是把日期写成'1999_12_16'这种下划线格式。MySQL的宽松模式会把这种字符串转成0000-00-00,等到按年份查询时结果为空,数据还不报错。更稳妥的是统一用'1999-12-16',或者显式调用STR_TO_DATE('1999_12_16', '%Y_%m_%d')。另外性别字段和职称字段存的是中文字符串,插入和查询时务必两边一致,比如'男'和'男 '中间带个空格都匹配不上。
5.3 查询套路:WHERE、日期计算、BETWEEN与IN
实验三的查询覆盖了SQL入门最重要的几个子句:
SELECT * FROM Courses; SELECT Sname, 2024 - YEAR(Birthday) AS Age FROM Students; 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 Sno, Sname FROM Students WHERE Speciality IN ('计算机', '软件工程'); SELECT Sno, Sname FROM Students WHERE Speciality NOT IN ('计算机', '软件工程');YEAR(Birthday)从日期里提取年份,2024减去年份得到年龄,这里用别名AS Age。BETWEEN是闭区间,等价于>=1997 AND <=1999。IN本质上是多个OR的简写,NOT IN则是取反。DISTINCT去重很关键,查“成绩不及格的学生学号”不去重会重复,因为一个学生可能多门课不及格。还有个细节:WHERE子句不能直接引用SELECT里定义的别名,比如WHERE Age > 20会报错,因为WHERE在逻辑上先于SELECT执行。
更新操作相对简单,但涉及表SC有复合主键,DELETE和UPDATE都要带全条件:
UPDATE SC SET Grade = Grade + 2 WHERE Grade < 60; DELETE FROM SC WHERE Sno = '201705001' AND Cno = 'cs301';执行前先跑一遍等价的SELECT看受影响行数,能避免把全表改错。这个习惯在真实运维里比任何语法知识都保命。
5.4 常见问题排查:五条踩坑实录
这一节把前面几章埋下的坑集中复盘,每条都是“现象→原因→解决”的结构。
第一坑,systemctl start greatsql后进程立刻消失。现象是服务状态显示failed,日志最后一行写着Data directory not initialized。原因是二进制包解压后没有初始化数据目录,mysqld拒绝直接干活。解决方法是补一次bin/mysqld --defaults-file=/etc/my.cnf --initialize-insecure --user=mysql,再启动。
第二坑,CREATE TABLE报语法错误error 1064。现象是SQL语句粘贴进去就报错,光标常常停在一个中文标点上。原因是全角逗号“,”混进了SQL,常见于中文输入法没切回来。解决方法是把编辑器显示标点打开,或者重新手敲一遍SQL,不要直接粘贴Word里的代码。实验二原稿里出现“id int,cid int”就是典型。
第三坑,建外键时报1215 Cannot add foreign key constraint。现象是ALTER TABLE ADD FOREIGN KEY执行失败。原因是子表SC.Sno和父表Students.Sno类型不一致,比如一个char(9)一个char(12),或者字符集、排序规则不同。解决方法是执行SHOW CREATE TABLE Students查看Sno定义,改成完全匹配的类型,必要时连COLLATE一并统一。
第四坑,日期插入后变成0000-00-00。现象是INSERT成功,但SELECT显示日期全是0,按年份查也查不到。原因是字符串格式不合法,MySQL宽松模式下没有抛错而是把非法日期归零。解决方法是改用'1999-12-16'标准格式,或者用STR_TO_DATE显式转换;同时可以查sql_mode,把STRICT_TRANS_TABLES加进去,让非法日期直接报错而不是静默归零。
第五坑,DROP TABLE带CASCADE报语法错误。现象是drop table students cascade;执行失败。原因是CASCADE是其他数据库的方言,MySQL的DROP TABLE不支持这个关键字。解决方法是去掉CASCADE直接删,顺序按先子后父。如果确实需要保留外键引用,可以考虑先删外键约束再删表,但教学实验里用不上。
这五条是老手在部署里都会碰到的类型。排错顺序也统一:先看错误码,再查对应对象的定义,最后检查字符集和数据类型。养成这个习惯,数据库实验基本不会有解决不了的问题。
6. 把实验变成可验证的套路:SHOW INDEX与EXPLAIN快速巡检
前面五章把安装、建表、查询都走了一遍,最后一个技巧能让你在做实验报告时多一层说服力:用SHOW INDEX和EXPLAIN验证索引到底有没有生效。实验二建了Student_Dept和Course_Cno两个索引,实验三的查询跑得飞快,但你凭什么说索引起了作用?凭EXPLAIN执行计划。
SHOW INDEX FROM Students; SHOW INDEX FROM Courses;这条输出里每一行代表一个索引,Key_name是索引名,Seq_in_index是索引内字段顺序,Column_name是列名,Non_unique为0表示唯一索引。建完Student_Dept后,这里应该能看到一条名为Student_Dept的记录。
然后再验证查询计划:
EXPLAIN SELECT * FROM Students WHERE Dno = 'CS'; EXPLAIN SELECT * FROM SC WHERE Sno = '201705001';EXPLAIN结果里的type字段是关键。type=ALL表示全表扫描,说明索引没被用上;type=ref或const表示走了索引,ref是普通索引等值命中的典型值,const是主键或唯一索引等值命中的典型值。如果建了索引但type还是ALL,最常见的原因是列上做了函数运算,比如WHERE YEAR(Birthday)=1999,这种写法会让索引失效,改成WHERE Birthday BETWEEN '1999-01-01' AND '1999-12-31'才能命中索引。把这两条EXPLAIN输出放进实验报告的结果分析里,比单纯说“查询成功”强得多。
把这套动作固化下来,是我拆完这份报告之后的直接收获。以前做实验总是建完表就开始写查询,出问题全靠猜;后来我强制自己每次装完数据库、建完表、写完SQL,都按同一个顺序走一遍:systemctl status检查服务、SHOW TABLE STATUS看表规模、EXPLAIN看执行计划。这个三件套走完,几乎所有低级错误都能提前暴露,而不是等数据填完才发现表结构错了。拆这份实验报告的过程里,最有价值的就是把这三步串成了一条固定巡检链路。如果你正要动手做同类的数据库实验,建议你也把这三步变成自己的习惯,希望帮到你。
本文还有配套的精品资源,点击获取