参考项目:CSDN《图书馆管理系统完整开发文档(Flask + Vue3 + Element Plus + MySQL)》,原项目基础功能:管理员登录、图书CRUD、书名模糊搜索;拓展:读者(借阅人)、图书分类、借阅记录、图书自定义属性,从需求→拆实体→字段选型→建表→避坑全流程,零基础跟着学会项目表设计。
文档目录
- 前置基础:做表前必学5个基础知识点
- 第一步:拆解项目需求(原项目+拓展需求)
- 第二步:从需求提取业务实体(核心:拆出几张表)
- 第三步:单表字段设计规范+数据类型选型
- 第四步:表与表关联关系设计(一对多核心)
- 第五步:完整建表SQL(基础版+完整版,可直接执行)
- 第六步:横表/竖表落地选型(复用之前知识点)
- 第七步:新手建表高频问题+错误解决方案
- 第八步:对接原Flask项目改造说明
一、前置基础:小白建表必懂5个基础规则
1.1 三大范式(精简白话版,不用死记硬背)
- 第一范式 :单个字段不能存多个数据(错误:
作者:鲁迅,老舍逗号拼接;正确:一个字段只存一个值)。 - 第二范式:一张表只干一件事(图书信息、借阅信息不能塞同一张表)。
- 第三范式:不冗余存储分类名称(分类名只存在分类表,图书表只存分类ID)。
1.2 统一字段命名规范(全项目统一,减少bug)
- 主键统一:
id INT AUTO_INCREMENT PRIMARY KEY(无业务含义自增ID,禁止手机号/身份证做主键); - 创建时间:
create_time DATETIME DEFAULT CURRENT_TIMESTAMP(自动存新增时间); - 更新时间:
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP(数据修改自动刷新时间); - 逻辑删除:
is_delete TINYINT DEFAULT 0(0正常、1删除,生产不用物理DELETE删数据); - 数据库、表统一字符集:
utf8mb4(兼容中文、emoji,杜绝乱码)。
1.3 常用字段类型选型速查表(小白必存,杜绝全用VARCHAR(255))
数据库字段有不熟悉的可以移步《数据库字段类型 + 核心关键字深度解析》 进行学习。
| 存储内容 | 推荐类型 | 举例 |
|---|---|---|
| ID、序号、数量 | INT | 图书id、库存数量 |
| 状态(正常/禁用、已还/未还) | TINYINT(1) | is_delete、is_return |
| 姓名、书名、分类名 | VARCHAR(50~100) | book.name、reader.name |
| 手机号 | VARCHAR(11) | reader.phone(固定11位不用int) |
| 日期(借书时间、出版日) | DATE/DATETIME | borrow.borrow_date |
| 金额(图书定价) | DECIMAL(10,2) | book.price(禁止float,避免精度丢失) |
1.4 索引使用规则
- 主键自带索引,不用重复建;
- 模糊查询字段、关联ID必须加索引:图书name模糊搜索、borrow.book_id、borrow.reader_id;
- 不常用字段不建索引(索引越多,新增修改越慢)。
1.5 横表&竖表选用口诀
- 固定属性、业务主体用横表:管理员、图书、读者、借阅记录;
- 后期不确定、随时新增字段用竖表:图书自定义规格(开本、页数)、读者标签(学生/会员)。
二、第一步:拆解项目需求(原项目+可拓展需求)
2.1 原项目原生需求(参考文档现有功能)
- 管理员账号密码登录后台;
- 图书:新增、编辑、删除、根据书名模糊搜索;
- 原始数据库仅
user(管理员)、book(图书)两张表。
2.2 商用拓展需求(真实图书馆必备,新增借阅全流程)
- 图书分类:图书归属文学/计算机/科普,分类可新增修改;
- 读者(借阅人)管理:录入借阅人姓名、手机号、身份证,停用/启用读者账号;
- 借阅管理:借书、登记借出日期、预计还书、实际还书、逾期标记;
- 图书拓展属性:部分图书额外存页数、装帧(精装/平装),属性不固定,不用频繁改book表。
2.3 需求提炼核心:所有操作围绕5类数据
管理员信息、图书基础信息、图书分类信息、读者信息、图书借阅流水、图书自定义扩展。
三、第二步:提取业务实体(拆表关键:一个实体一张横表)
从需求拆分6张数据表:
sys_admin:后台管理员(原user表,改名规范);book_category:图书分类(新增,解决分类冗余);book:图书主表(原项目book,新增分类id、库存);reader:读者/借阅人(新增,单独存借阅人信息);borrow_record:借阅记录表(中间关联表,绑定图书+读者);book_attr:图书扩展竖表(动态属性,key-value结构)。
新手易错:不要把借阅人姓名直接存借阅表(改姓名需要全表批量修改,严重冗余),必须单独建reader横表。
四、第三步:逐张表字段详细设计(带注释+设计理由)
4.1 sys_admin 管理员表(横表,原user优化)
| 字段名 | 类型 | 约束 | 注释&设计说明 |
|---|---|---|---|
| id | INT AUTO_INCREMENT | PRIMARY KEY | 管理员唯一编号 |
| username | VARCHAR(50) | NOT NULL UNIQUE | 登录账号,唯一索引,不能重复 |
| password | VARCHAR(60) | NOT NULL | 密码,正式项目加密存储,测试明文 |
| create_time | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| is_delete | TINYINT | DEFAULT 0 | 逻辑删除0正常1删除 |
4.2 book_category 图书分类表(横表,解耦分类冗余)
| 字段名 | 类型 | 约束 | 注释&设计说明 |
|---|---|---|---|
| id | INT AUTO_INCREMENT | PRIMARY KEY | 分类主键 |
| category_name | VARCHAR(50) | NOT NULL | 分类名称:计算机、文学 |
| sort | INT | DEFAULT 0 | 后台分类排序 |
| create_time | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
设计目的:如果直接在book存分类名称,分类改名需要全表update,违背第三范式。
4.3 book 图书主表(横表,原项目book优化升级)
| 字段名 | 类型 | 约束 | 注释&设计说明 |
|---|---|---|---|
| id | INT AUTO_INCREMENT | PRIMARY KEY | 图书主键 |
| book_name | VARCHAR(100) | NOT NULL | 书名,加索引用于模糊搜索 |
| author | VARCHAR(50) | NULL | 作者 |
| press | VARCHAR(100) | NULL | 出版社 |
| category_id | INT | NULL | 关联分类id,指向book_category.id |
| stock | INT | DEFAULT 1 | 库存剩余可借数量 |
| create_time | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| update_time | DATETIME | ON UPDATE CURRENT_TIMESTAMP | 修改自动更新 |
| is_delete | TINYINT | DEFAULT 0 | 逻辑删除 |
4.4 reader 读者(借阅人)横表(新增核心表)
| 字段名 | 类型 | 约束 | 注释&设计说明 |
|---|---|---|---|
| id | INT AUTO_INCREMENT | PRIMARY KEY | 读者编号(借阅人ID) |
| reader_name | VARCHAR(20) | NOT NULL | 借阅人姓名 |
| phone | VARCHAR(11) | UNIQUE | 手机号,唯一,催还使用 |
| id_card | VARCHAR(18) | NULL | 身份证号 |
| status | TINYINT | DEFAULT 1 | 1正常可借书,0禁用 |
| register_time | DATETIME | DEFAULT CURRENT_TIMESTAMP | 办卡时间 |
| is_delete | TINYINT | DEFAULT 0 | 逻辑删除 |
4.5 borrow_record 借阅记录表(中间关联横表)
| 字段名 | 类型 | 约束 | 注释&设计说明 |
|---|---|---|---|
| id | INT AUTO_INCREMENT | PRIMARY KEY | 借阅流水号 |
| book_id | INT | NOT NULL | 关联book.id,加索引 |
| reader_id | INT | NOT NULL | 关联reader.id,加索引 |
| borrow_date | DATE | NOT NULL | 借出日期 |
| expect_return | DATE | NOT NULL | 预计归还日期 |
| real_return | DATE | NULL | 实际归还,空=未还 |
| borrow_status | TINYINT | DEFAULT 1 | 1在借,2已还,3逾期 |
| create_time | DATETIME | DEFAULT CURRENT_TIMESTAMP | 记录生成时间 |
一对多关系:1个读者→N条借阅记录、1本图书→N条借阅记录。
4.6 book_attr 图书扩展竖表(动态属性专用)
| 字段名 | 类型 | 约束 | 注释&设计说明 |
|---|---|---|---|
| id | INT AUTO_INCREMENT | PRIMARY KEY | 主键 |
| relation_id | INT | NOT NULL | 关联book.id |
| attr_key | VARCHAR(50) | NOT NULL | 英文key:page(页数)/format(装帧) |
| attr_value | VARCHAR(200) | NULL | 属性值 |
规范:key存英文,中文名称后端/字典映射,不在本表存中文避免冗余。
五、第四步:全表关联关系汇总(小白画图记忆)
book_category(1) → book(N):一个分类多本图书;book(1) → borrow_record(N):一本书多次被借阅;reader(1) → borrow_record(N):一个读者借多本书;book(1) → book_attr(N):一本书多条自定义扩展属性。
六、第五步:完整可执行建表SQL(分2套:原项目精简版、完整版)
6.1 精简版(适配原Flask项目原生功能,仅admin+book,和原文档数据库一致)
sql
CREATE DATABASE IF NOT EXISTS library CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE library;
-- 管理员表(原user)
CREATE TABLE sys_admin(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '管理员主键',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录账号',
password VARCHAR(60) NOT NULL COMMENT '登录密码',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
is_delete TINYINT DEFAULT 0
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入默认管理员 admin/123456
INSERT INTO sys_admin(username,password) VALUES('admin','123456');
-- 图书基础表(原生项目)
CREATE TABLE book(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '图书ID',
name VARCHAR(100) NOT NULL COMMENT '书名',
author VARCHAR(50) COMMENT '作者',
press VARCHAR(100) COMMENT '出版社',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP,
is_delete TINYINT DEFAULT 0,
INDEX idx_book_name(name) -- 模糊查询索引
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
6.2 完整版(含分类、读者、借阅、扩展竖表,商用完整库)
sql
USE library;
-- 1.分类表
CREATE TABLE book_category(
id INT PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(50) NOT NULL,
sort INT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
)ENGINE=InnoDB CHARSET=utf8mb4;
-- 2.优化后图书表
ALTER TABLE book ADD COLUMN category_id INT COMMENT '关联分类ID',ADD INDEX idx_cid(category_id);
-- 3.读者表
CREATE TABLE reader(
id INT PRIMARY KEY AUTO_INCREMENT,
reader_name VARCHAR(20) NOT NULL,
phone VARCHAR(11) UNIQUE,
id_card VARCHAR(18),
status TINYINT DEFAULT 1,
register_time DATETIME DEFAULT CURRENT_TIMESTAMP,
is_delete TINYINT DEFAULT 0
)ENGINE=InnoDB CHARSET=utf8mb4;
-- 4.借阅记录表
CREATE TABLE borrow_record(
id INT PRIMARY KEY AUTO_INCREMENT,
book_id INT NOT NULL,
reader_id INT NOT NULL,
borrow_date DATE NOT NULL,
expect_return DATE NOT NULL,
real_return DATE NULL,
borrow_status TINYINT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_bid(book_id),
INDEX idx_rid(reader_id)
)ENGINE=InnoDB CHARSET=utf8mb4;
-- 5.图书扩展竖表
CREATE TABLE book_attr(
id INT PRIMARY KEY AUTO_INCREMENT,
relation_id INT NOT NULL,
attr_key VARCHAR(50) NOT NULL,
attr_value VARCHAR(200),
INDEX idx_relation(relation_id)
)ENGINE=InnoDB CHARSET=utf8mb4;
七、第六步:原Flask项目对接改造思路
- 原有接口不动:登录、图书新增/编辑/删除/搜索继续使用原5个API,适配精简版admin、book;
- 新增功能新增接口:分类CRUD、读者CRUD、借书/还书接口、图书属性新增接口;
- 竖表查询处理 :查询图书详情时,
book LEFT JOIN book_attr,后端CASE行转列,前端展示中文属性名。
八、第七步:小白建表高频错误+解决方案(重点避坑)
错误1:全字段统一用VARCHAR(255)
- 问题:数字、日期用字符串,排序、筛选异常、索引低效;
- 解决:严格按类型速查表选型,状态用TINYINT、日期用DATE。
错误2:借阅人姓名存入borrow_record,不单独建reader
- 问题:读者改名需要批量修改全借阅记录,数据不一致;
- 解决:reader单独横表,借阅表只存reader_id关联。
错误3:需要新增图书属性直接ALTER加字段
- 问题:频繁改表、字段杂乱,迭代困难;
- 解决:固定字段在book,动态字段存入book_attr竖表。
错误4:忘记给书名、关联ID加索引,图书搜索卡顿
- 问题:上万条数据后模糊查询极慢;
- 解决:
book.name、borrow.book_id、borrow.reader_id必建索引。
错误5:使用utf字符集(无mb4)
- 问题:生僻汉字、emoji存入乱码;
- 解决:全库、全表统一
utf8mb4。
错误6:物理DELETE删除图书数据
- 问题:删除后无法恢复,历史借阅记录关联失效;
- 解决:用
is_delete=1逻辑删除,查询默认筛选WHERE is_delete=0。
九、第八步:新手建表落地五步总结(万能设计流程,任何项目通用)
- 梳理需求:罗列所有页面增删改查功能;
- 拆分实体:一类独立数据一张横表;
- 设计字段:统一命名、精准选字段类型、添加必要约束;
- 梳理关系:一对多用外键ID关联,多对多新建中间表;
- 区分横竖:固定属性横表、动态扩展竖表,按需建索引。
十、补充常用关联查询SQL(开发直接复用)
sql
-- 查询某个读者所有借阅图书(张三)
SELECT r.reader_name,b.book_name,br.borrow_date,br.borrow_status
FROM reader r
LEFT JOIN borrow_record br ON r.id=br.reader_id
LEFT JOIN book b ON br.book_id=b.id
WHERE r.reader_name='张三';
-- 查询图书+自定义属性(竖表行转列)
SELECT b.name,
MAX(CASE WHEN ba.attr_key='page' THEN ba.attr_value END) AS 页数,
MAX(CASE WHEN ba.attr_key='format' THEN ba.attr_value END) AS 装帧
FROM book b
LEFT JOIN book_attr ba ON b.id=ba.relation_id
WHERE b.id=1;