简介本资源是华中科技大学《数据库系统原理实践——以MySQL为例》课程的配套实验代码与文档合集面向数据库初学者、计算机专业本科生及备考数据库认证的学习者旨在通过系统化实操深化对存储过程、事务控制、触发器、并发隔离、索引实现及数据库设计等核心原理的理解。压缩包共92个文件含62个SQL脚本覆盖建库建表、查询优化、事务管理、触发器定义等、7个Java程序用于数据库应用开发、6个C源码实现B树索引关键逻辑、4个Shell脚本备份与日志恢复、以及PDF报告模板、DrawIO设计图、JPG/PNG示意图等辅助材料整体仅1.42MB轻量易用。已有66人下载学习资源按实验模块分文件夹组织命名规范如“6. MySQL - 存储过程与事务”每关卡对应独立SQL文件辅以任务书、评分细则与结构化说明文档便于循序渐进开展实验、对照验证与自主复盘。1. 这不是又一份MySQL安装指南华中科技大学《数据库系统原理实践》课设包里藏着的是让你真正“看见”事务隔离、锁机制和查询优化器运行轨迹的实操入口你下载了“华中科技大学 数据库系统原理实践 - 以MySQL为例.zip”解压后发现一堆.sql文件、README.md和几个Python脚本——但没看到任何图形界面、没有预装Docker镜像、也没有一键启动的Web控制台。别急着删这恰恰是它最硬核的价值它不教你“怎么连上MySQL”而是逼你亲手构造出能让ACID特性“显形”的最小实验场。比如用两个并发连接执行同一段UPDATESELECT观察READ-COMMITTED下为什么第二次SELECT能看到第一次未提交的变更或者在InnoDB引擎下用SHOW ENGINE INNODB STATUS解析死锁日志里那几行“TRANSACTION”和“WAITING FOR THIS LOCK TO BE GRANTED”的真实指向。这不是面向应用开发的MySQL速成班而是面向系统级理解的“数据库解剖实验包”。适合正在啃《数据库系统概念》第六章、写课程设计却卡在“事务调度图不会画”、或面试前想把“MVCC怎么实现”讲出内存结构细节的本科生与转岗工程师。它不替代官方文档但它把文档里抽象的“意向锁”“间隙锁”“聚簇索引B树分裂”全变成你本地终端里可复现、可打断、可逐行调试的现场。2. 从零构建可调试的MySQL实验环境避开Docker封装黑盒用原生二进制包定制配置直触InnoDB内核行为华科这份实践包的设计逻辑非常清晰所有实验必须能在无容器、无GUI、纯命令行环境下稳定复现。这意味着你不能依赖XAMPP或WAMP这类集成包——它们默认关闭了关键调试日志且MySQL进程由服务管理器托管无法直接attach gdb。我们必须回归MySQL官方二进制分发包而非apt/yum源手动控制mysqld启动参数让InnoDB的锁等待、事务状态、缓冲池命中率全部暴露在终端里。2.1 下载与校验为什么必须用mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz而非deb/rpm包华科实践包中的SQL脚本大量使用SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED和SELECT * FROM information_schema.INNODB_TRX这些特性在MySQL 8.0.30才对information_schema视图提供完整支持。而Ubuntu/Debian官方源默认提供的是5.7或8.0.28以下版本其INNODB_TRX表缺少TRX_WAIT_STARTED字段导致实验脚本中“检测事务阻塞时间”的逻辑直接报错。因此必须从 dev.mysql.com/downloads/mysql/ 下载Linux Generic版二进制包注意不是RPM或DEB。校验环节不可跳过# 下载后先验证SHA256以8.0.33为例 wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz.sha256 sha256sum -c mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz.sha256 # 输出应为mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz: OK提示.tar.xz包解压后是完整可执行目录无需install所有bin/mysqld、share/charsets、lib/plugin都在其中。这是调试型部署的基石——你随时可以替换lib/plugin/ha_innodb.so来测试自定义存储引擎补丁虽然本次实践不用但架构上留出了这个能力。2.2 初始化与最小化配置仅保留InnoDB、禁用Query Cache、强制记录锁等待解压后创建独立数据目录避免污染系统MySQLmkdir -p ~/mysql-practice/{data,log,conf} cd ~/mysql-practice # 初始化数据目录--no-defaults确保不读取/etc/my.cnf ./mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld \ --no-defaults \ --initialize-insecure \ --datadir./data \ --basedir./mysql-8.0.33-linux-glibc2.12-x86_64关键在my.cnf配置——华科实践包要求所有实验必须在可重现的确定性环境下运行因此我们禁用所有可能引入随机性的模块# ~/mysql-practice/conf/my.cnf [mysqld] # 基础路径 basedir /home/yourname/mysql-practice/mysql-8.0.33-linux-glibc2.12-x86_64 datadir /home/yourname/mysql-practice/data socket /home/yourname/mysql-practice/mysql.sock port 3307 # 避免与系统MySQL冲突 # 强制InnoDB单线程刷脏页消除IO调度干扰 innodb_flush_method O_DIRECT innodb_io_capacity 200 innodb_io_capacity_max 400 # 关键开启所有InnoDB调试日志但只记录锁相关事件 innodb_print_all_deadlocks ON innodb_status_output ON innodb_status_output_locks ON # 必须开启否则SHOW ENGINE INNODB STATUS不显示锁信息 # 禁用Query CacheMySQL 8.0已默认移除但显式声明防误 query_cache_type 0 query_cache_size 0 # 事务隔离级别默认设为READ-COMMITTED华科实验统一基准 transaction_isolation READ-COMMITTED # 缓冲池大小设为固定值避免动态调整影响缓存命中率观测 innodb_buffer_pool_size 256M innodb_buffer_pool_instances 1 # 日志路径绝对路径相对路径在mysqld启动时会解析失败 log_error /home/yourname/mysql-practice/log/error.log slow_query_log_file /home/yourname/mysql-practice/log/slow.log general_log_file /home/yourname/mysql-practice/log/general.log参数说明innodb_status_output_locks ON是本实践的生命线。它让SHOW ENGINE INNODB STATUS输出中包含---TRANSACTION块下的LOCK WAIT详情否则你只能看到“Lock wait timeout exceeded”却无法定位是哪一行被哪个事务锁住。port 3307避免与宿主机MySQL冲突所有实验脚本里的mysql -P3307都依赖此设定。2.3 启动与验证用mysqladmin ping确认服务就绪用ps aux | grep mysqld确认进程参数启动服务注意必须指定配置文件路径否则mysqld忽略my.cnf~/mysql-practice/mysql-8.0.33-linux-glibc2.12-x86_64/bin/mysqld \ --defaults-file~/mysql-practice/conf/my.cnf \ --user$(whoami) \ --console注意--console参数强制将错误日志输出到终端方便实时观察初始化过程。若看到mysqld: ready for connections即成功。此时新开终端验证mysql -u root -P3307 -e SELECT VERSION(), transaction_isolation; # 应输出8.0.33 和 READ-COMMITTED3. 复现华科实践核心实验用三组SQLPython脚本亲手触发并捕获MVCC版本链、间隙锁阻塞、索引下推失效华科这份实践包的精华不在SQL语法教学而在用最小数据集构造出教科书级现象。我们选取三个最具代表性的实验事务可见性边界MVCC、幻读与间隙锁Gap Lock、索引下推优化ICP失效。每个实验都提供可直接运行的脚本并附带验证命令。3.1 实验一MVCC版本链可视化——为什么READ-COMMITTED下两次SELECT看到不同结果实践包中mvcc_demo.sql构造了一个极简场景Session A开启事务后UPDATE一行Session B在A未提交时SELECT同一行B应看到旧版本A提交后B再次SELECT应看到新版本。但仅靠SELECT无法证明版本链存在——我们需要INFORMATION_SCHEMA.INNODB_TRX和INNODB_LOCK_WAITS交叉验证。-- mvcc_demo.sql需在两个终端分别执行 -- Session A先执行 START TRANSACTION; UPDATE accounts SET balance balance 100 WHERE id 1; -- 此时不要COMMIT -- Session B后执行 START TRANSACTION; SELECT * FROM accounts WHERE id 1; -- 返回旧balance SELECT * FROM information_schema.INNODB_TRX WHERE trx_mysql_thread_id CONNECTION_ID(); -- 查看当前事务ID -- 再开一个终端执行 mysql -u root -P3307 -e SELECT * FROM information_schema.INNODB_TRX\G | grep -E (trx_id|trx_state|trx_started)关键验证点Session B的trx_id应大于Session A的trx_idInnoDB分配递增事务ID且Session B的trx_state为RUNNING而非LOCK WAIT——证明它没被阻塞而是通过ReadView找到了历史版本。此时查看data/ibdata1文件InnoDB系统表空间的十六进制内容可定位到该行记录的DB_ROLL_PTR字段指向undo log物理地址——这就是MVCC版本链的物理锚点。3.2 实验二间隙锁Gap Lock阻塞——为什么DELETE WHERE age BETWEEN 20 AND 30会锁住(30,40)区间华科实验刻意设计了非主键范围查询来触发间隙锁。gap_lock_demo.sql创建表时未建索引迫使InnoDB在二级索引缺失时对聚簇索引加间隙锁CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(20), age INT ); INSERT INTO users VALUES (1,Alice,18), (2,Bob,25), (3,Charlie,35); -- Session A执行 START TRANSACTION; DELETE FROM users WHERE age BETWEEN 20 AND 30; -- 锁住(18,25)和(25,35)间隙 -- Session B尝试插入age28的记录 INSERT INTO users VALUES (4,David,28); -- 被阻塞验证间隙锁存在的唯一可靠方式是SHOW ENGINE INNODB STATUSmysql -u root -P3307 -e SHOW ENGINE INNODB STATUS\G | sed -n /TRANSACTIONS/,/FILE I/O/p | grep -A 10 LOCK WAIT输出中会出现类似*** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 29 page no 3 n bits 72 index PRIMARY of table test.users trx id 12345 lock_mode X locks gap before rec insert intention waitinglocks gap before rec明确标识这是间隙锁insert intention waiting表明Session B的插入意向锁被阻塞。若建了INDEX(age)则锁类型变为lock_mode X locks rec but not gap记录锁验证命令输出会完全不同。3.3 实验三索引下推ICP失效——为什么LIKE %abc让联合索引失去ICP优化icp_demo.sql创建联合索引(status, created_at)但查询条件WHERE statusactive AND created_at 2023-01-01在MySQL 8.0.33中默认启用ICP。要观察失效场景需构造LIKE模糊查询CREATE TABLE orders ( id INT PRIMARY KEY, status VARCHAR(20), created_at DATETIME, INDEX idx_status_time (status, created_at) ); -- 插入测试数据... -- 执行 EXPLAIN FORMATTREE SELECT * FROM orders WHERE status shipped AND created_at LIKE 2023-01%;关键观察点EXPLAIN FORMATTREE输出中若出现Using index condition则ICP生效若为Using where; Using index则ICP失效因为LIKE 2023-01%可走索引范围扫描但LIKE %01会导致ICP退化。华科实践要求你修改created_at条件为LIKE %01-01再对比两次EXPLAIN的rows估算值——后者会显著增大证明ICP未过滤行全量回表后由Server层过滤。4. 避坑华科实践包在Linux环境下高频翻车的5个血泪现场与自救方案这份实践包在CentOS 7/Ubuntu 20.04上运行时有5个位置极易因环境差异导致实验失败。以下是我在3所高校助教实践中收集的真实报错及解决路径按发生频率排序4.1 现象ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock原因实践包脚本默认连接/tmp/mysql.sock但我们的my.cnf中socket /home/yourname/mysql-practice/mysql.sock。MySQL客户端找不到socket文件自动fallback到/tmp。解决方案1推荐在my.cnf中增加[client]段显式指定socket路径[client] socket /home/yourname/mysql-practice/mysql.sock port 3307方案2所有mysql命令加--socket参数如mysql -u root -P3307 --socket/home/yourname/mysql-practice/mysql.sock4.2 现象ERROR 1045 (28000): Access denied for user rootlocalhost原因MySQL 8.0默认认证插件为caching_sha2_password而部分老版本mysql-client不支持。实践包中的Python脚本若用mysql-connector-python8.0.23会因认证协议不匹配拒绝连接。解决创建兼容用户在mysql命令行中执行CREATE USER prac_userlocalhost IDENTIFIED WITH mysql_native_password BY prac123; GRANT ALL PRIVILEGES ON test.* TO prac_userlocalhost; FLUSH PRIVILEGES;Python脚本中连接字符串改为mysql://prac_user:prac123localhost:3307/test4.3 现象ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause原因MySQL 8.0默认启用ONLY_FULL_GROUP_BYSQL模式而实践包中部分聚合查询未严格遵循GROUP BY规则如SELECT name, COUNT(*) FROM users未将name放入GROUP BY。解决临时关闭实验期间SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,ONLY_FULL_GROUP_BY,));永久关闭在my.cnf的[mysqld]段添加sql_mode STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION4.4 现象mysqld: Cant create/write to file /home/yourname/mysql-practice/log/error.log (Errcode: 13)原因Linux SELinux策略或目录权限限制。mysqld进程以yourname用户运行但log/目录可能被创建为root权限如用sudo解压zip包。解决chown -R yourname:yourname ~/mysql-practice若SELinux启用sudo setsebool -P mysqld_disable_trans 1临时方案或sudo semanage fcontext -a -t mysqld_log_t /home/yourname/mysql-practice/log(/.*)?后restorecon -Rv ~/mysql-practice/log4.5 现象Python脚本执行cursor.execute(SELECT SLEEP(10))时卡死CtrlC无效原因SLEEP()函数在MySQL中是服务器端阻塞Python的mysql-connector默认未设置connection_timeout导致整个进程挂起。解决在Python连接字符串中添加connection_timeout5参数或代码中设置cnx mysql.connector.connect(..., connection_timeout5)更彻底方案改用time.sleep(10)在Python层模拟延迟避免占用MySQL连接5. 进阶技巧用pt-query-digest分析慢查询日志把华科实验中的“性能差异”转化为可量化的I/O与CPU消耗华科实践包中多个实验如索引优化对比、JOIN算法选择最终落点都是“为什么这个更快”。光看EXPLAIN的rows估算不够——你需要知道实际磁盘读了多少页、CPU花了多少毫秒。这里介绍一个被严重低估的利器Percona Toolkit中的pt-query-digest它能把MySQL慢查询日志slow.log转化为带火焰图的性能报告。5.1 生成可分析的慢查询日志关闭日志采样强制记录所有实验SQL华科实验要求精确对比不同SQL的执行开销因此必须关闭慢查询日志的采样率默认只记录超过long_query_time的SQL改为记录所有执行超时100ms的语句# 在my.cnf的[mysqld]段追加 slow_query_log ON slow_query_log_file /home/yourname/mysql-practice/log/slow.log long_query_time 0.1 log_queries_not_using_indexes OFF # 关闭避免干扰主实验 min_examined_row_limit 0注意long_query_time 0.1100ms是平衡精度与日志体积的合理值。实验中所有SELECT、UPDATE均会记录但SET、BEGIN等管理语句不会。5.2 执行实验并提取慢日志用pt-query-digest生成HTML报告假设你刚运行完index_optimization.sql对比有无索引的JOIN性能此时慢日志已积累数据# 安装pt-query-digest需Perl环境 sudo apt install percona-toolkit # Ubuntu/Debian # 或 sudo yum install percona-toolkit # CentOS/RHEL # 解析日志--report参数生成文本摘要--outputslow-log.html生成交互式HTML pt-query-digest \ --report \ --outputslow-log.html \ --filter (\$event-{arg} ~ m/^SELECT.*FROM orders.*JOIN users/i) \ ~/mysql-practice/log/slow.log生成的slow-log.html中你会看到Query Time Distribution柱状图显示95%查询耗时200ms但有3次峰值达1200ms——对应无索引JOIN的三次执行Profile表格按Response Time排序第一行显示SELECT ... JOIN语句其Rows examine列数值是索引版的8.3倍Rows sent相同证明I/O放大是主因Item Analysis点击具体SQL展开Explain原始输出和Query_plan可视化树直观看到type: ALL全表扫描vstype: ref索引查找。5.3 关键洞察从报告中定位InnoDB缓冲池失效点在slow-log.html的Metrics标签页重点关注InnoDB Buffer Pool Hit Rate缓冲池命中率。华科实验中当你执行SELECT * FROM big_table WHERE id 100000无索引时该指标会从99%骤降至62%——这意味着83%的页需要从磁盘读取。而同一查询加了索引后命中率维持在98%以上。这个数字比rows估算更真实它告诉你性能差异的本质是磁盘I/O成本 vs 内存访问成本而非CPU计算。我的习惯每次做完索引优化实验必跑一次pt-query-digest把HTML报告中的Query_time和Rows_examined两列截图存档。半年后复习时看到当年那个Rows_examined: 1245892的红色高亮立刻想起“没建索引的JOIN有多痛”。这种具象记忆比背诵“B树减少IO次数”管用十倍。希望帮到你。本文还有配套的精品资源点击获取