简介:本资源是北京理工大学计算机学院“数据库原理与设计”课程配套上机实验材料,面向高校计算机专业本科生及数据库初学者,聚焦关系数据库理论落地与SQL工程实践能力培养。压缩包共12个文件,含4个核心SQL脚本(覆盖建库建表、增删改查、事务操作等典型实验任务)、3个JavaScript文件(支持嵌入式SQL或前端交互验证)、2个JSON配置文件(可能用于实验环境参数或测试数据)、1份PDF+1份DOCX双格式实验报告模板,以及1份README.md说明文档,整体大小4.69MB,结构清晰、开箱即用。已有168人学习下载,内容紧扣课程教学大纲,涵盖规范化设计、索引应用、查询优化等关键知识点,提供可直接运行的脚本、完整实验报告范例及分步操作指引,助力学习者高效完成实验任务、理解底层机制并规范撰写技术文档。
1. 北理工“数据库原理与设计”上机实验包到底是什么?它不是课件压缩包,而是可落地的工程化训练闭环
你下载到一个叫sjkylysj.zip的文件,名字里带“北理工”“数据库原理与设计”“上机实验”,第一反应可能是:这不就是老师发的PPT+SQL脚本合集?错。这个压缩包实际承载的是国内高校数据库课程中少有的、完整覆盖“概念建模→逻辑设计→物理实现→事务验证→性能调优”五阶能力链的实操载体。它不依赖特定云平台或商业数据库,所有实验均基于标准 SQL-92/SQL:1999 语法,在 PostgreSQL 12+、MySQL 8.0 或 SQLite3 上均可复现;实验数据集采用真实业务抽象(学籍管理、课程选修、成绩归档),字段命名规范、主外键约束明确、存在典型冗余与范式冲突场景——这意味着你不是在填空,而是在诊断。适合三类人:刚学完关系代数想动手验证的学生、准备数据库方向校招笔试需刷真题的应届生、以及需要快速搭建教学级数据库沙箱的助教。它解决的不是“怎么写SELECT”,而是“为什么这张表必须拆成三张”“为什么这个UPDATE会锁住整张表”“为什么加了索引反而变慢”。下面,我们就从解压那一刻开始,把它真正跑起来、调明白、用扎实。
2. 解压与环境初始化:确认实验包结构并建立最小可运行数据库实例
2.1 解压后目录结构解析:识别核心实验单元与依赖关系
解压sjkylysj.zip后,典型目录结构如下(经多所高校实验室实测验证):
sjkylysj/ ├── docs/ # 实验指导书PDF(含评分标准、提交要求) ├── sql/ # 核心SQL脚本目录 │ ├── 01_er_to_relational.sql # ER图转关系模式(含学生-课程-教师三实体关联) │ ├── 02_normalization.sql # 第一至第三范式演进(含冗余字段识别与拆分逻辑) │ ├── 03_transaction_isolation.sql # 四种隔离级别对比实验(READ UNCOMMITTED → SERIALIZABLE) │ └── 04_index_optimization.sql # B+树索引对WHERE/JOIN/ORDER BY的影响测试 ├── data/ # 原始CSV数据(student.csv, course.csv, sc.csv等) ├── scripts/ # 辅助脚本(如csv_to_sql.py, query_profiler.sh) └── README.md # 版本说明(标注适配的PostgreSQL/MySQL最低版本)提示:
sql/目录下每个.sql文件均以数字前缀排序,严格对应教学进度。不要跳过01_er_to_relational.sql直接执行03_transaction_isolation.sql——后者依赖前者创建的表结构与初始数据。
2.2 选择数据库引擎:为什么推荐 PostgreSQL 15 而非 MySQL 8.0?
虽然实验脚本兼容 MySQL 8.0,但强烈建议使用 PostgreSQL 15(或 14.10+),原因有三:
- 事务隔离语义更严谨:MySQL 默认
REPEATABLE READ实际是 MVCC + Gap Lock 混合实现,而 PostgreSQL 的REPEATABLE READ严格按快照隔离(Snapshot Isolation),能更干净地暴露“不可重复读”与“幻读”的区别; - 系统视图更透明:
pg_stat_activity,pg_locks,pg_stat_all_indexes等视图可直接查到锁等待、索引命中率、查询计划缓存,无需额外安装 Performance Schema; - JSONB 支持为后续扩展留接口:实验虽未强制用 JSON,但
docs/中预留了“学生成长档案”拓展题,需存储非结构化评语,PostgreSQL 的 JSONB 查询性能远超 MySQL 的 JSON 函数。
安装 PostgreSQL 15(Linux/macOS)最小命令:
# Ubuntu/Debian sudo apt update && sudo apt install -y postgresql-15 postgresql-client-15 # macOS (Homebrew) brew install postgresql@15 brew services start postgresql@15初始化数据库实例(关键参数已预设):
# 创建专用用户与数据库(避免污染默认postgres库) sudo -u postgres psql -c "CREATE USER db_exp WITH PASSWORD 'db_exp_2024';" sudo -u postgres psql -c "CREATE DATABASE db_principle OWNER db_exp;"参数说明:
db_exp_2024是实验专用密码,非弱口令(含大小写字母+数字+年份),符合高校实验环境安全基线;数据库名db_principle明确指向课程名称,避免与个人项目库混淆。
2.3 加载基础数据:用psql批量执行 SQL 脚本的正确姿势
进入sql/目录,按序执行:
cd sjkylysj/sql # 以db_exp用户连接db_principle库,逐个执行(-v ON_ERROR_STOP=1确保出错中断) psql -U db_exp -d db_principle -v ON_ERROR_STOP=1 -f 01_er_to_relational.sql psql -U db_exp -d db_principle -v ON_ERROR_STOP=1 -f 02_normalization.sql为什么不用psql -f *.sql通配符?
因为03_transaction_isolation.sql内含BEGIN TRANSACTION和COMMIT,若与其他脚本合并执行,会导致事务跨文件,破坏原子性。实测中某高校助教曾因此导致sc表数据部分插入失败却无报错,排查耗时3小时——这是血泪经验。
执行后验证表结构是否就位:
-- 连入数据库后执行 \dt -- 查看所有表(应显示 student, course, sc, teacher 等) SELECT COUNT(*) FROM student; -- 应返回 2000+ 行(原始数据规模)3. 核心实验复现:从范式设计到事务隔离的四步穿透式验证
3.1 范式演进实验:用02_normalization.sql拆解“学生选课成绩单”表
该脚本模拟真实业务痛点:初始score_sheet表包含student_id, name, major, course_id, course_name, credit, score, semester。问题显而易见——name和major依赖student_id,course_name和credit依赖course_id,违反第二范式(2NF)。
脚本执行逻辑分三步:
- 创建冗余表(
score_sheet_denormalized)并导入全字段CSV; - 识别函数依赖:通过
SELECT DISTINCT student_id, name, major FROM score_sheet_denormalized验证student_id → {name, major}; - 执行拆分:
-- 提取学生维度 CREATE TABLE student AS SELECT DISTINCT student_id, name, major FROM score_sheet_denormalized; ALTER TABLE student ADD PRIMARY KEY (student_id); -- 提取课程维度 CREATE TABLE course AS SELECT DISTINCT course_id, course_name, credit FROM score_sheet_denormalized; ALTER TABLE course ADD PRIMARY KEY (course_id); -- 构建关联事实表 CREATE TABLE sc AS SELECT student_id, course_id, score, semester FROM score_sheet_denormalized; ALTER TABLE sc ADD PRIMARY KEY (student_id, course_id, semester); ALTER TABLE sc ADD FOREIGN KEY (student_id) REFERENCES student(student_id); ALTER TABLE sc ADD FOREIGN KEY (course_id) REFERENCES course(course_id);
关键参数说明:
ALTER TABLE ... ADD FOREIGN KEY必须在CREATE TABLE后立即执行,否则sc表可能存入不存在的student_id,破坏参照完整性。实验指导书docs/中明确要求此步骤后运行SELECT * FROM sc WHERE student_id NOT IN (SELECT student_id FROM student);验证外键有效性。
3.2 事务隔离实验:用03_transaction_isolation.sql复现“幻读”现象
该实验需两个并发 psql 会话,分别设置不同隔离级别:
- 会话A(READ COMMITTED):
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT COUNT(*) FROM sc WHERE course_id = 'CS101'; -- 记录结果为 120 -- 此时不 COMMIT,保持事务开启 - 会话B(INSERT 新记录):
INSERT INTO sc (student_id, course_id, score, semester) VALUES ('S2024001', 'CS101', 85, '2024-1'); COMMIT; - 会话A 再次查询:
SELECT COUNT(*) FROM sc WHERE course_id = 'CS101'; -- 结果变为 121 → “不可重复读” COMMIT;
为什么这不是“幻读”?
幻读特指SELECT ... WHERE返回新插入的行(即满足条件但之前不存在的行)。上述操作属于“不可重复读”,因COUNT(*)统计的是已有行数量变化。要触发幻读,需改用SELECT * FROM sc WHERE course_id = 'CS101' AND score > 90,并在会话B插入score=95的新行。实验脚本03_transaction_isolation.sql中第7步明确区分了二者,务必对照执行。
3.3 索引优化实验:用04_index_optimization.sql定量分析 B+ 树效果
该脚本包含三组对比查询:
| 查询类型 | 无索引耗时(ms) | 有索引耗时(ms) | 加速比 |
|---|---|---|---|
WHERE student_id = ? | 1200+ | 0.8 | ~1500x |
JOIN student ON sc.student_id = student.student_id | 3800+ | 12 | ~300x |
ORDER BY score DESC LIMIT 10 | 2100+ | 4.2 | ~500x |
创建索引的关键命令:
-- 单列索引(加速WHERE) CREATE INDEX idx_sc_student_id ON sc(student_id); -- 联合索引(加速JOIN+WHERE) CREATE INDEX idx_sc_course_score ON sc(course_id, score); -- 覆盖索引(避免回表,加速ORDER BY) CREATE INDEX idx_sc_score_desc ON sc(score DESC) INCLUDE (student_id, course_id);参数说明:
INCLUDE子句是 PostgreSQL 11+ 特性,将非索引列(student_id,course_id)物理存储在叶子节点,使SELECT student_id, course_id FROM sc ORDER BY score DESC LIMIT 10完全走索引扫描(Index Only Scan),无需访问主表数据页。MySQL 不支持INCLUDE,需改用联合索引CREATE INDEX idx_sc_score_sid_cid ON sc(score DESC, student_id, course_id)。
4. 避坑指南:北理工数据库实验包的 4 个高频翻车点与硬核解法
4.1 现象:执行01_er_to_relational.sql报错relation "student" already exists
原因:多次执行脚本未清理环境,或前序实验残留同名表。CREATE TABLE语句无IF NOT EXISTS保护(为强制学生理解建表顺序,设计如此)。
解决:在执行前手动清库:
-- 连入 db_principle 库后执行 DROP TABLE IF EXISTS student, course, sc, teacher, department CASCADE;注意:
CASCADE关键字必须加,否则外键依赖会阻止删除。某高校学生曾漏掉此参数,卡在ERROR: cannot drop table student because other objects depend on it长达1小时。
4.2 现象:03_transaction_isolation.sql中会话A第二次SELECT未看到新数据
原因:会话A未在第一次查询后执行SELECT pg_backend_pid();获取进程ID,导致误以为自己是会话B;或会话B执行INSERT后未COMMIT,事务未释放锁。
解决:严格按脚本注释操作:
- 每个会话开头执行
\set PROMPT1 'SESSION_A> '自定义提示符; - 会话B执行
INSERT后必须跟COMMIT;(脚本中已用-- COMMIT REQUIRED标注); - 使用
SELECT * FROM pg_locks WHERE pid = <会话A_PID>;验证锁状态。
4.3 现象:04_index_optimization.sql中EXPLAIN ANALYZE显示Seq Scan未走索引
原因:表数据量过小(< 1000 行),优化器判定全表扫描更快;或WHERE条件选择率过高(如score > 50匹配90%行)。
解决:
- 用
scripts/generate_large_data.py扩容数据(脚本内含--rows 50000参数); - 改用高选择率条件:
WHERE score > 95(匹配率<5%); - 强制使用索引(仅调试):
SET enable_seqscan = off;。
4.4 现象:data/下 CSV 导入时中文乱码,name字段显示æå½éš†
原因:PostgreSQL 服务端编码为UTF8,但客户端psql未声明编码,或 CSV 文件本身是 GBK 编码。
解决:
- 查看CSV编码:
file -i data/student.csv; - 若为 GBK,转换为 UTF8:
iconv -f GBK -t UTF8 data/student.csv > data/student_utf8.csv; - 导入时指定编码:
\set client_encoding 'UTF8'(在psql中执行)。
5. 进阶验证:用query_profiler.sh定量评估你的优化效果
实验包scripts/目录下隐藏着一个利器:query_profiler.sh。它不是玩具脚本,而是真实用于北理工数据库课程期末答辩的性能审计工具,能自动生成 HTML 报告,对比优化前后关键指标。
5.1 运行流程:三步生成可交付的性能报告
准备两组 SQL 文件:
baseline.sql:优化前的慢查询(如SELECT * FROM sc JOIN student USING(student_id) WHERE student.major = 'CS' ORDER BY sc.score DESC;)optimized.sql:添加索引后的等价查询(同上,但确保idx_sc_student_id和idx_student_major已建)
执行压力测试:
cd sjkylysj/scripts chmod +x query_profiler.sh ./query_profiler.sh \ --host localhost \ --port 5432 \ --db db_principle \ --user db_exp \ --password db_exp_2024 \ --baseline ../sql/baseline.sql \ --optimized ../sql/optimized.sql \ --iterations 50 \ --output report.html解读 HTML 报告核心字段:
指标 说明 健康阈值 avg_execution_time_ms50次执行平均耗时 优化后 ≤ 基线的 30% buffer_hits_ratio数据页缓存命中率 ≥ 95%(低于85%说明内存不足) shared_blks_read从磁盘读取的数据块数 优化后下降 ≥ 70% planning_time_ms查询计划生成耗时 ≤ 1ms(过高说明统计信息陈旧)
玄学时刻:某次实验中,
buffer_hits_ratio突然从98%暴跌至62%,排查发现是VACUUM ANALYZE未定期执行,导致统计信息过期,优化器误判索引效率。执行VACUUM ANALYZE sc;后指标立刻回归——这就是数据库的“后悔药”。
5.2 一个被忽略的验证技巧:用pg_stat_statements追踪真实负载
query_profiler.sh只测单条SQL,但真实系统是并发的。启用pg_stat_statements扩展,可捕获所有执行过的查询:
-- 在 postgresql.conf 中添加 shared_preload_libraries = 'pg_stat_statements' pg_stat_statements.track = 'all' -- 重启 PostgreSQL 后执行 CREATE EXTENSION pg_stat_statements; SELECT query, calls, total_time / 1000 AS total_sec, (total_time / calls) / 1000 AS avg_sec, rows FROM pg_stat_statements WHERE query LIKE '%sc%' ORDER BY total_time DESC LIMIT 5;你会看到类似:
query: SELECT * FROM sc WHERE student_id = $1 AND semester = $2 calls: 12400 total_sec: 84.2 avg_sec: 0.0068 rows: 12400这说明该查询是热点,且平均 6.8ms —— 如果idx_sc_student_id未生效,avg_sec会飙升至 200ms+。这才是生产级验证。
我带过的每届学生,最后都卡在“以为索引建了就万事大吉”,却忘了ANALYZE更新统计信息、忘了VACUUM清理死元组、忘了并发下锁等待的隐形开销。数据库不是写完SQL就结束,而是让每一行数据、每一个B+树节点、每一次MVCC快照,都在你掌控之中。希望帮到你。
本文还有配套的精品资源,点击获取