简介:这份通讯录管理系统数据库课程设计报告以 SQL Server 与 Java 为技术栈,完整展示了一个个人通讯录管理系统的数据库设计与实现过程,适合正在完成数据库原理与应用课程设计的学生参考。报告按照标准设计流程展开,从需求分析、数据字典与数据流程图,到概念结构设计中的局部 E-R 图,再到关系模式转化与优化,并给出了创建数据库表、视图、存储过程的 SQL 代码。运行与维护部分设计了登录、联系人、分组、查询、增加、修改、删除等界面,方案结构清晰、步骤完整。资源为 1 个 docx 文档,压缩包大小 840KB,已有 74 人学习。对需要快速理解数据库课程设计规范、掌握从需求分析到实施维护全流程写法的人而言,是一份可直接参考的范例。
1. 通讯录管理系统数据库课程设计:先别急着写文档,先拆表
拿到“通讯录管理系统数据库课程设计报告”这个题目,大多数人第一反应是去找模板、调格式、画流程图。但数据库课程设计的评分点从来不在页数上,而在你能不能把“联系人、分组、电话、地址”这些日常概念翻译成一组合理的表结构、外键、索引和事务。通讯录是管理信息系统的经典样例,数据量不大,但实体关系覆盖了一对多、多对多、逻辑删除、冗余字段取舍等数据库设计的核心考点,非常适合用来把原理讲透。这篇文章以 MySQL 为例,从建表 SQL、增删改查与事务、连接池配置,一直讲到备份恢复和课程设计报告里必须写清楚的数据验证部分。你可以直接照着实现,也能在写报告时拿这些内容替换掉那些空泛的“系统设计”章节。
2. 通讯录业务建模:从联系人、分组到多对多关系的表结构设计
2.1 实体与属性拆解:通讯录里到底有几张表
通讯录最朴素的需求是记录“某个人有手机号、公司电话、邮箱、家庭住址”。如果只做一张大表,字段会随联系人数量膨胀,重复数据也会越来越多,比如同一个家庭地址被多条记录重复保存。按数据库设计的基本范式,通讯录至少可以拆成三类实体:
联系人实体(contact),存放姓名、性别、备注、创建时间等不变或低频变化的信息; 分组实体(contact_group),存放“同事”“家人”“客户”这类分类信息; 联系方式实体(contact_contact),存放手机、邮箱、地址、座机等属性,每个联系人可以有零到多条。
这里需要理解一个关键点:联系人和分组之间不是一对多,而是多对多。一个联系人可以同时属于“同事”和“项目组”,一个分组里也会有多个联系人。要表达这种关系,不能简单地在联系人表里加一个 group_id 字段,而必须引入一张中间关联表。这是课程设计报告里最容易扣分的地方,也是评委最爱追问的问题。
属性字段的拆分同样有讲究。把手机和邮箱拆到联系方式的子表里,除了满足第一范式,更重要的是避免了“联系人只有一部手机,却有五个手机号”的尴尬。至于地址,一般会单独建立一个地址表或直接放在联系方式表里用 type 字段区分,因为地址通常伴随类型属性(家庭、公司),而且查询频率低于电话。
2.2 建库建表 SQL:一份可以直接套用的通讯录数据库脚本
下面给出一个最小可用但足够完整的 MySQL 建表脚本,覆盖了上述实体关系、外键约束和常用索引。你可以把这套脚本作为课程设计报告里的“数据库物理设计”章节。
CREATE DATABASE IF NOT EXISTS address_book DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE address_book; -- 分组表:存放通讯录分组信息 CREATE TABLE contact_group ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分组主键', group_name VARCHAR(50) NOT NULL COMMENT '分组名称', parent_id BIGINT UNSIGNED DEFAULT NULL COMMENT '父分组id,支持树形分组', sort_order INT NOT NULL DEFAULT 0 COMMENT '同层级排序值,越小越靠前', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (id), UNIQUE KEY uk_group_name (group_name, parent_id), CONSTRAINT fk_group_parent FOREIGN KEY (parent_id) REFERENCES contact_group (id) ON DELETE CASCADE ) ENGINE=InnoDB COMMENT='通讯录分组表'; -- 联系人表 CREATE TABLE contact ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系人主键', name VARCHAR(30) NOT NULL COMMENT '姓名', gender TINYINT NOT NULL DEFAULT 0 COMMENT '性别: 0保密 1男 2女', avatar_url VARCHAR(255) DEFAULT NULL COMMENT '头像地址', remark VARCHAR(255) DEFAULT NULL COMMENT '备注', is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记: 0正常 1已删除', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_contact_name (name), KEY idx_contact_deleted (is_deleted, created_at) ) ENGINE=InnoDB COMMENT='联系人主表'; -- 联系人与分组的多对多关联表 CREATE TABLE contact_group_mapping ( contact_id BIGINT UNSIGNED NOT NULL COMMENT '联系人id', group_id BIGINT UNSIGNED NOT NULL COMMENT '分组id', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (contact_id, group_id), KEY idx_mapping_group (group_id), CONSTRAINT fk_map_contact FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE, CONSTRAINT fk_map_group FOREIGN KEY (group_id) REFERENCES contact_group (id) ON DELETE CASCADE ) ENGINE=InnoDB COMMENT='联系人与分组关联表'; -- 联系方式表:一个联系人多条联系方式 CREATE TABLE contact_contact ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '联系方式主键', contact_id BIGINT UNSIGNED NOT NULL COMMENT '所属联系人id', contact_type TINYINT NOT NULL COMMENT '联系方式类型: 1手机 2座机 3邮箱 4地址', contact_value VARCHAR(255) NOT NULL COMMENT '联系方式内容,如手机号或邮箱地址', is_primary TINYINT NOT NULL DEFAULT 0 COMMENT '是否为主要联系方式: 1是 0否', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_contact_contact (contact_id, contact_type), CONSTRAINT fk_contact_detail FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE ) ENGINE=InnoDB COMMENT='联系方式表';逐段说明脚本里的设计取舍。
contact_group 表里设置了 parent_id 外键自引用,目的是支持“子分组”结构。实际课程设计里如果不需要树形分组,可以删掉这个字段,但保留它能让报告里的 E-R 图多一个自关联的讨论点。uk_group_name 唯一索引限制了同一父分组下不能出现重名分组,这是一个很容易被忽略的业务规则。
contact 表特意加了 is_deleted 字段而不是直接物理删除。通讯录的“删除联系人”在很多系统中需要可恢复、可审计,逻辑删除字段配合查询条件 WHERE is_deleted = 0 就能实现软删除。这也是在课程设计答辩时用来展示“数据安全考虑”的加分项。
contact_group_mapping 表使用联合主键 (contact_id, group_id) 而不是额外加一个自增 id,一是能保证同一联系人不会在同一分组中重复出现,二是节省索引空间。额外的 idx_mapping_group 索引是为了支持“查某个分组下所有联系人”的反向查询,否则联合主键只有最左前缀的 contact_id 能用上,按 group_id 过滤时会走全表扫描。
contact_contact 表把手机、邮箱、地址统一放一个字段 contact_value,用 type 区分。这样做的好处是以后要新增“微信号”“传真号”时不需要改表结构,只需要加一个枚举值;坏处是无法对某一类联系方式单独做字段约束。作为课程设计,这个设计明显优于给每个联系方式建独立字段,因为它是用扩展性换来的更稳结构。
2.3 主键策略、字符集与常见约束的选型逻辑
主键采用 BIGINT UNSIGNED AUTO_INCREMENT,这是通讯录场景的默认选择。自增主键在 InnoDB 里是聚簇索引,新记录写入时追加到索引末尾,减少页分裂。如果你看过雪花 ID 或 UUID 主键的方案,通讯录这种规模根本没有必要,UUID 不仅占用更大存储,还会让聚簇索引随机写入,性能反而下降。报告中如果提到主键策略,直接写清楚“数据量不超过千万级别时自增主键成本最低”即可。
字符集选 utf8mb4 而不是 utf8,核心原因有两个:一是 utf8 在 MySQL 里实际只支持最多 3 字节,存不了 emoji 表情和部分生僻字;二是通讯录的备注、地址字段里出现特殊符号的概率很高。排序规则用 utf8mb4_general_ci,它的性能略好于 utf8mb4_unicode_ci,在这个量级上两者结果几乎无差异。
| 约束类型 | 本设计中的应用位置 | 说明 |
|---|---|---|
| PRIMARY KEY | contact.id、contact_group.id、映射表联合主键 | 保证记录唯一性,InnoDB 聚簇索引的载体 |
| FOREIGN KEY | 映射表、联系方式表、分组的自关联 | 保证引用完整性,防止孤儿数据 |
| UNIQUE KEY | uk_group_name | 业务规则约束:同层分组不能重名 |
| NOT NULL | name、contact_value、mapping 外键字段 | 防止关键信息缺失 |
| DEFAULT | is_deleted、sort_order、created_at | 减少应用层必须传值的负担 |
外键在实际企业开发中经常被禁用,但在课程设计报告里应该保留并加以说明。外键保证的引用完整性在“单个联系人必须存在才能插入联系方式”这类约束上非常直观,还能展示你理解数据库层面完整性的价值。后续讲到批量导入时你会发现,有外键反而能提前拦截脏数据。
3. 通讯录增删改查与事务控制:从 SQL 模板到死锁排查
3.1 联系人核心操作 SQL:增删改查的完整写法
这一节的内容可以直接搬进报告的“数据库操作设计”章节。四条核心 SQL 模板覆盖了联系人管理的主体逻辑,每一条都要能解释清楚“为什么这样写”。
-- 新增联系人,并同时建立他与“同事”分组的关系 INSERT INTO contact (name, gender, remark) VALUES ('张明', 1, '技术部同事'); SET @new_contact_id = LAST_INSERT_ID(); INSERT INTO contact_group_mapping (contact_id, group_id) VALUES (@new_contact_id, 3); INSERT INTO contact_contact (contact_id, contact_type, contact_value, is_primary) VALUES (@new_contact_id, 1, '13800138000', 1), (@new_contact_id, 3, 'zhangming@example.com', 0);-- 修改联系人基本信息和主要手机号 UPDATE contact SET name = '张明远', remark = '技术部高级工程师' WHERE id = 1001 AND is_deleted = 0; UPDATE contact_contact SET contact_value = '13900139000' WHERE contact_id = 1001 AND contact_type = 1 AND is_primary = 1;-- 将联系人移出某个分组,但不删除联系人本身 DELETE FROM contact_group_mapping WHERE contact_id = 1001 AND group_id = 3;-- 分页查询某个分组下的所有联系人 SELECT c.id, c.name, c.gender, cc.contact_value AS mobile FROM contact_group_mapping m JOIN contact c ON c.id = m.contact_id AND c.is_deleted = 0 LEFT JOIN contact_contact cc ON cc.contact_id = c.id AND cc.contact_type = 1 AND cc.is_primary = 1 WHERE m.group_id = 3 ORDER BY c.created_at DESC LIMIT 0, 20;上面的新增操作使用 LAST_INSERT_ID() 获取自增主键,比先在应用层查询再插入的方式更可靠,因为它在同一会话内返回的是刚刚插入的 id。多值 INSERT 一次插入三条联系方式,减少与数据库的往返次数。
修改语句里带上了 is_deleted = 0 条件,防止对已删除的旧数据做误操作。删除操作只移除分组关联关系而非删除主表数据,这是多对多关系中标准做法。
查询语句是四条 SQL 中最值得在报告中展开的一条。它包含了一次 INNER JOIN 和一次 LEFT JOIN,前者保证只返回存在于分组映射中的联系人,后者因为联系人可能没有手机号,用 LEFT JOIN 保证联系人本身不丢失。注意 LEFT JOIN 的 ON 条件里放了 cc.contact_type = 1 AND cc.is_primary = 1,这能把“取联系人的主要手机号”这个业务逻辑局限在关联阶段,而不是放到 WHERE 里。一旦放到 WHERE,LEFT JOIN 会被隐式改回 INNER JOIN,没有手机号的联系人就会被过滤掉,这是个非常经典的 SQL 陷阱。
3.2 事务与 ACID:批量导入联系人的正确姿势
单条插入失败时数据不会有大问题,但实际通讯录系统经常需要批量导入,例如从 Excel 或另一个旧系统迁移 500 个联系人。此时每条联系人还要同时插入关联分组和联系方式,三步操作必须在一个事务内完成。
// 以伪代码展示事务边界,实际语言为 Java public void importContacts(List<ImportItem> items) { Connection conn = dataSource.getConnection(); try { conn.setAutoCommit(false); // 关闭自动提交,开启事务 for (ImportItem item : items) { long contactId = insertContact(conn, item); // 插入联系人主表 insertGroupMapping(conn, contactId, item.getGroupIds()); insertContactWays(conn, contactId, item.getPhones(), item.getEmails()); } conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任一步失败整体回滚 throw new RuntimeException("批量导入失败", e); } finally { conn.setAutoCommit(true); conn.close(); } }这段代码的事务边界很清晰:把多个 INSERT 操作包在 commit 与 rollback 之间,保证联系人主表、映射表、联系方式表要么全部写入,要么全部不写入。如果没有事务,可能出现联系人主表有了记录,联系方式插入时报错,导致联系人缺手机号的脏数据。
真正在报告里值得写的是隔离级别的选择。MySQL InnoDB 默认隔离级别是 REPEATABLE READ,它通过 MVCC 让普通 SELECT 不阻塞写操作,所以在通讯录这种读多写少的场景里完全够用。虽然理论上 REPEATABLE READ 存在幻读问题,但 InnoDB 通过间隙锁解决了大部分场景下的幻读。连接配置中不需要特意把隔离级别改成 READ COMMITTED。要注意的反而是在批量导入时,对同一资源加锁的顺序不一致可能导致死锁。
3.3 数据库并发锁与死锁:通讯录场景的排查方法
两个连接同时执行“把 1001 号联系人移入分组 A”和“把 1001 号联系人移入分组 B”时,如果事务内处理了其他分组的写入,就有可能出现两个事务各持一把锁,又同时想拿对方锁的情况。InnoDB 检测到死锁后会自动回滚其中一个事务,应用层只需要捕获 DeadlockLoserDataAccessException 并重试。
SHOW ENGINE INNODB STATUS\G;执行这条命令后,重点看 LATEST DETECTED DEADLOCK 段落。它会列出两个事务各自持有的锁和等待的锁,最常见的现象是事务 1 持有 contact_group 表中 id=1 的行锁,等待 id=2 的行锁;事务 2 持有 id=2 的行锁,等待 id=1 的行锁。
避免死锁的方法在通讯录系统里很实用:让所有涉及多条分组记录的操作按固定顺序执行。比如先处理 id 较小的分组,再处理 id 较大的分组,所有事务都遵守这个规则,循环等待就不会形成。如果并发量确实高,可以把 innodb_lock_wait_timeout 从默认的 50 秒调小,比如 10 秒,让锁等待快速失败并触发应用层重试,而不是让用户卡在界面上。
这里还要澄清一个课程设计里的常见误区:把数据库死锁等同于“并发就是坏东西”。并发是常态,死锁只是并发中多种资源竞争时的一种失败模式,报告里应该写清楚你的策略是“检测到死锁后重试,而不是用 SELECT FOR UPDATE 把所有操作串行化”。
4. 从 JDBC 到连接池:通讯录数据库访问层的配置与优化
4.1 数据库连接参数:每个配置项都在防什么
通讯录应用启动后,第一步是建立到数据库的连接。课程设计报告里如果贴连接配置,光写一行 jdbc URL 是不够的,参数含义需要逐条解释。
jdbc:mysql://localhost:3306/address_book?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true- useUnicode=true 与 characterEncoding=utf8:保证中文姓名和备注在传输过程中不会乱码。
- useSSL=false:本地开发环境不需要加密通道,开启反而增加握手耗时,同时可以避免自签名证书报错。
- serverTimezone=Asia/Shanghai:MySQL 驱动高版本要求显式指定时区,否则插入 DATETIME 字段时可能偏差 8 小时。
- allowPublicKeyRetrieval=true:MySQL 8 的 caching_sha2_password 认证方式在非 SSL 连接下需要这个参数才能获取公钥,本地调试时建议开启,生产环境应换成 SSL。
应用层不要用 DriverManager 每次都创建新连接,这种方式在通讯录系统里会导致每次查询都经历 TCP 握手、认证、释放连接的完整流程,并发一高就会出现连接超时。标准做法是业务代码中只调用 DataSource.getConnection(),把连接生命周期交给连接池管理。
4.2 连接池参数表:HikariCP 在通讯录场景下的推荐配置
HikariCP 是目前使用最广的连接池,配置参数不多,但含义精确。下面是一份通讯录管理系统常见的配置参考,不是越大越好。
spring: datasource: hikari: minimum-idle: 5 maximum-pool-size: 10 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000| 参数名 | 推荐值 | 参数作用与坑点 |
|---|---|---|
| minimum-idle | 5 | 连接池长期保持的最小连接数,过高浪费资源 |
| maximum-pool-size | 10 | 核心参数。通讯录系统并发通常几十人同时操作,10 个连接足够;设成 50 反而让数据库线程频繁切换 |
| connection-timeout | 30000 | 等待连接的最长时间(毫秒),超过即抛异常;本地调试可以调小到 3000 快速发现问题 |
| idle-timeout | 600000 | 空闲连接存活时间,仅在连接数超过最小空闲数时生效 |
| max-lifetime | 1800000 | 连接最大存活时间。必须小于数据库 wait_timeout,否则连接被服务端断开后客户端仍在使用 |
连接池大小不要拍脑袋设大。通讯录的每个请求通常只占用一次连接执行两次 SQL,数据库的 CPU 和 IO 是完全空闲的,连接数再大也不会让查询变快,只会增加锁等待和内存占用。一个经验公式是“连接数 = 核心线程数 × 2 + 磁盘数”,对课程设计系统来说 10 到 20 足够。
4.3 分页性能与索引:查询优化在通讯录系统里的落地
通讯录列表页最常见的操作是分页查询。LIMIT 0, 20 在小数据量下没问题,但当联系人表超过十万行,用户翻到第 10000 条记录时,LIMIT 10000, 20 需要先扫描并丢弃前 10000 条记录。
优化写法是记录上一页最后一条记录的 id,下一页用 WHERE id > 上一页最大 id LIMIT 20 的方式。把深分页的偏移量问题变成了简单的索引范围扫描。课程设计报告里如果能写明白这两种写法的差别,优化部分的分数基本稳了。
索引配合查询语句来建。上一章的分组查询条件涉及 group_id、is_deleted、created_at 和 contact_type,可以在映射表上建复合索引 (group_id, contact_id)。虽然映射表的联合主键已经是 (contact_id, group_id),但它无法满足按 group_id 查询的场景,所以需要额外索引。验证索引是否生效用 EXPLAIN:
EXPLAIN SELECT c.id, c.name FROM contact_group_mapping m JOIN contact c ON c.id = m.contact_id AND c.is_deleted = 0 WHERE m.group_id = 3;执行结果中关注 type 列。如果是 ref,说明走的是非唯一索引等值查询,合理;如果是 ALL,说明发生了全表扫描,需要检查索引。key_len 列表示使用的索引长度,值越大说明使用越充分。课程设计报告里通常需要截取一张 EXPLAIN 的结果图并做分析。
5. 初始化、备份与数据库脚本:课程设计报告里最有分量的收尾
5.1 初始化脚本组织:建库、建用户、导入基础数据
课程设计交付时,数据库需要能在一台新机器上快速复现。把手工点击的操作全部写成脚本,不仅方便答辩演示,也体现工程化意识。下面是 init.sql 的常见组织方式:
-- 0. 初始化数据库与账号,避免直接用 root 连接 CREATE DATABASE IF NOT EXISTS address_book DEFAULT CHARACTER SET utf8mb4; CREATE USER IF NOT EXISTS 'address_user'@'localhost' IDENTIFIED BY 'Address@123'; GRANT SELECT, INSERT, UPDATE, DELETE, INDEX ON address_book.* TO 'address_user'@'localhost'; FLUSH PRIVILEGES; -- 1. 表结构(建表语句同第 2 章) -- 2. 基础字典数据(如默认分组“我的好友”“同事”“家人”) INSERT INTO contact_group (group_name, sort_order) VALUES ('我的好友', 1), ('同事', 2), ('家人', 3);给业务账号最小化的权限而不是用 root 操作,是数据库安全的底线。这里只授了 SELECT、INSERT、UPDATE、DELETE 和 INDEX,没有给 DROP 和 ALTER,可以防止应用逻辑出错时误删表结构。报告里写“最小权限原则”,比空谈安全意识更有说服力。
5.2 备份与恢复:mysqldump 参数对照与验证方法
通讯录数据虽然不像订单那么重要,但课程设计中也需要演示备份恢复能力。mysqldump 是 MySQL 自带的逻辑备份工具,关键在于参数选择:
# 备份 address_book 库,包含建库语句和数据 mysqldump -uaddress_user -pAddress@123 \ --single-transaction \ --default-character-set=utf8mb4 \ --set-gtid-purged=OFF \ --databases address_book > address_book_backup.sql # 恢复备份 mysql -uroot -p < address_book_backup.sql--single-transaction 是 InnoDB 表的核心参数。它通过开启一个一致性的可重复读事务来获取备份点,备份期间不会锁表,也不会影响通讯录系统的正常读写。--set-gtid-purged=OFF 是为了避免备份文件里包含基于 GTID 的复制标记,本地恢复时不产生干扰。恢复命令直接使用 root 因为初始化脚本中可能包含建库语句。
备份文件不能只生成不管,恢复后的验证同样重要。恢复完成后执行 COUNT(*) 对比联系人和分组数量,再抽查几个联系人,看手机号和分组是否完整。
5.3 课程设计报告收尾前必做的三项数据验证
最后这三条 SQL 是课程设计的数据质量检查清单,也是答辩时面对“你的数据库设计有没有问题”这类问题的底气所在。
-- 1. 检查孤儿数据:存在联系方式但联系人主表没有对应记录 SELECT COUNT(*) AS orphan_count FROM contact_contact cc LEFT JOIN contact c ON c.id = cc.contact_id WHERE c.id IS NULL; -- 2. 统计每个分组的联系人数量(验证多对多关系正确性) SELECT g.group_name, COUNT(m.contact_id) AS member_count FROM contact_group g LEFT JOIN contact_group_mapping m ON m.group_id = g.id GROUP BY g.id, g.group_name ORDER BY member_count DESC; -- 3. 检查未设置主要手机号的联系人 SELECT c.id, c.name FROM contact c WHERE c.is_deleted = 0 AND NOT EXISTS ( SELECT 1 FROM contact_contact cc WHERE cc.contact_id = c.id AND cc.is_primary = 1 AND cc.contact_type = 1 );第一条 SQL 能验证外键是否符合预期;如果之前建立外键时用了 WITH CHECK 选项,理论上查出来为 0,但早期数据导入时如果跳过约束可能会留下脏数据。第二条 SQL 同时反映映射表的关联正确性和 GROUP BY 的写法,报告里可以贴出来作为“数据库完整性检查”的证据。第三条 SQL 是最实用的业务检测,通讯录系统必须保证每个正常联系人至少有一个主要手机号,否则前端展示列表时会出现空手机号的联系人。
如果这三条 SQL 查询结果全部符合预期,数据库课程设计的核心部分基本可以收工。剩下的工作是把这些 SQL 执行结果截图、加上文字说明、附上 E-R 图,放进报告的“数据库验证”章节即可。
本文还有配套的精品资源,点击获取