图书管理系统数据库设计实战:MySQL 8.0 下 5 张核心表与 3 个关键视图构建
·
MySQL 8.0 图书管理系统数据库实战:核心表设计与性能优化
1. 系统架构与数据库规划
图书管理系统作为典型的信息管理类应用,其数据库设计需要兼顾功能完整性与查询效率。我们采用MySQL 8.0作为数据库引擎,充分利用其窗口函数、CTE(公共表表达式)和原子DDL等新特性。系统主要包含以下核心模块:
- 图书信息管理
- 读者账户管理
- 借阅/归还业务处理
- 统计分析报表
数据库设计遵循第三范式(3NF)原则,同时针对高频查询场景做适当反范式化优化。系统整体架构采用分层设计:
应用层(Java/Python等) → 服务层 → 数据访问层 → MySQL 8.0数据库
2. 核心表结构设计与实现
2.1 图书信息表(book)
CREATE TABLE `book` (
`book_id` VARCHAR(20) NOT NULL COMMENT '图书编号(ISBN+自增后缀)',
`isbn` VARCHAR(17) NOT NULL COMMENT '国际标准书号',
`title` VARCHAR(100) NOT NULL COMMENT '书名',
`author` VARCHAR(50) NOT NULL COMMENT '作者',
`publisher` VARCHAR(50) NOT NULL COMMENT '出版社',
`publish_date` DATE NOT NULL COMMENT '出版日期',
`category_id` INT NOT NULL COMMENT '分类ID',
`price` DECIMAL(10,2) NOT NULL COMMENT '定价',
`total_copies` INT NOT NULL DEFAULT 1 COMMENT '总副本数',
`available_copies` INT NOT NULL COMMENT '可借副本数',
`location` VARCHAR(30) COMMENT '馆藏位置',
`cover_url` VARCHAR(255) COMMENT '封面图片URL',
`description` TEXT COMMENT '图书简介',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`book_id`),
UNIQUE KEY `idx_isbn` (`isbn`),
KEY `idx_category` (`category_id`),
KEY `idx_title` (`title`),
KEY `idx_author` (`author`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2.2 读者信息表(reader)
CREATE TABLE `reader` (
`reader_id` VARCHAR(20) NOT NULL COMMENT '读者证号',
`name` VARCHAR(30) NOT NULL COMMENT '姓名',
`gender` ENUM('M','F','O') NOT NULL COMMENT '性别',
`birth_date` DATE COMMENT '出生日期',
`phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
`email` VARCHAR(50) COMMENT '电子邮箱',
`address` VARCHAR(100) COMMENT '联系地址',
`id_card` VARCHAR(18) COMMENT '身份证号',
`reader_type` TINYINT NOT NULL COMMENT '读者类型(1学生/2教师/3职工)',
`max_borrow` INT NOT NULL DEFAULT 5 COMMENT '最大借阅量',
`account_status` TINYINT NOT NULL DEFAULT 1 COMMENT '账户状态(1正常/2挂失/3注销)',
`register_date` DATE NOT NULL COMMENT '注册日期',
`expire_date` DATE NOT NULL COMMENT '有效期至',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`reader_id`),
UNIQUE KEY `idx_id_card` (`id_card`),
KEY `idx_phone` (`phone`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2.3 借阅记录表(borrow_record)
CREATE TABLE `borrow_record` (
`record_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`book_id` VARCHAR(20) NOT NULL COMMENT '图书ID',
`reader_id` VARCHAR(20) NOT NULL COMMENT '读者ID',
`borrow_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '借出日期',
`due_date` DATETIME NOT NULL COMMENT '应还日期',
`return_date` DATETIME COMMENT '实际归还日期',
`renew_count` TINYINT NOT NULL DEFAULT 0 COMMENT '续借次数',
`operator_id` VARCHAR(20) NOT NULL COMMENT '操作员ID',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1借出/2已还/3逾期/4丢失)',
`fine_amount` DECIMAL(10,2) DEFAULT 0 COMMENT '罚款金额',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`record_id`),
KEY `idx_book_id` (`book_id`),
KEY `idx_reader_id` (`reader_id`),
KEY `idx_due_date` (`due_date`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2.4 图书分类表(category)
CREATE TABLE `category` (
`category_id` INT NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`category_name` VARCHAR(30) NOT NULL COMMENT '分类名称',
`parent_id` INT DEFAULT NULL COMMENT '父分类ID',
`category_code` VARCHAR(20) NOT NULL COMMENT '分类代码',
`description` VARCHAR(200) COMMENT '分类描述',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`category_id`),
UNIQUE KEY `idx_category_code` (`category_code`),
KEY `idx_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2.5 系统用户表(sys_user)
CREATE TABLE `sys_user` (
`user_id` VARCHAR(20) NOT NULL COMMENT '用户ID',
`username` VARCHAR(30) NOT NULL COMMENT '用户名',
`password` VARCHAR(100) NOT NULL COMMENT '加密密码',
`real_name` VARCHAR(30) NOT NULL COMMENT '真实姓名',
`phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
`email` VARCHAR(50) COMMENT '电子邮箱',
`avatar` VARCHAR(255) COMMENT '头像URL',
`role_id` INT NOT NULL COMMENT '角色ID',
`last_login` DATETIME COMMENT '最后登录时间',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1启用/0禁用)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`),
KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3. 关键视图设计与查询优化
3.1 未归还图书视图(v_unreturned_books)
CREATE VIEW v_unreturned_books AS
SELECT
r.record_id,
b.book_id,
b.title,
b.isbn,
rd.reader_id,
rd.name AS reader_name,
r.borrow_date,
r.due_date,
DATEDIFF(r.due_date, CURRENT_DATE()) AS remaining_days,
CASE
WHEN r.due_date < CURRENT_DATE() THEN DATEDIFF(CURRENT_DATE(), r.due_date)
ELSE 0
END AS overdue_days,
CASE
WHEN r.due_date < CURRENT_DATE() THEN
LEAST(DATEDIFF(CURRENT_DATE(), r.due_date) * 0.5, 50)
ELSE 0
END AS estimated_fine
FROM borrow_record r
JOIN book b ON r.book_id = b.book_id
JOIN reader rd ON r.reader_id = rd.reader_id
WHERE r.return_date IS NULL;
3.2 读者借阅统计视图(v_reader_borrow_stats)
CREATE VIEW v_reader_borrow_stats AS
WITH reader_stats AS (
SELECT
reader_id,
COUNT(*) AS total_borrowed,
SUM(CASE WHEN return_date IS NULL THEN 1 ELSE 0 END) AS current_borrowed,
SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) AS total_overdue,
MAX(CASE WHEN status = 3 THEN DATEDIFF(return_date, due_date) ELSE 0 END) AS max_overdue_days,
SUM(fine_amount) AS total_fines
FROM borrow_record
GROUP BY reader_id
)
SELECT
r.reader_id,
r.name,
r.reader_type,
r.max_borrow,
rs.total_borrowed,
rs.current_borrowed,
(r.max_borrow - rs.current_borrowed) AS remaining_quota,
rs.total_overdue,
rs.max_overdue_days,
rs.total_fines,
CASE
WHEN rs.total_overdue > 3 THEN '高风险'
WHEN rs.total_overdue > 0 THEN '中风险'
ELSE '低风险'
END AS risk_level
FROM reader r
LEFT JOIN reader_stats rs ON r.reader_id = rs.reader_id;
3.3 图书借阅排行视图(v_book_borrow_rank)
CREATE VIEW v_book_borrow_rank AS
SELECT
b.book_id,
b.title,
b.author,
b.publisher,
c.category_name,
b.total_copies,
b.available_copies,
COUNT(br.record_id) AS borrow_count,
RANK() OVER (ORDER BY COUNT(br.record_id) DESC) AS borrow_rank,
AVG(DATEDIFF(COALESCE(br.return_date, CURRENT_DATE), br.borrow_date)) AS avg_borrow_days
FROM book b
LEFT JOIN borrow_record br ON b.book_id = br.book_id
LEFT JOIN category c ON b.category_id = c.category_id
GROUP BY b.book_id, b.title, b.author, b.publisher, c.category_name, b.total_copies, b.available_copies;
4. 事务处理与并发控制
4.1 借书事务处理
DELIMITER //
CREATE PROCEDURE sp_borrow_book(
IN p_book_id VARCHAR(20),
IN p_reader_id VARCHAR(20),
IN p_operator_id VARCHAR(20),
OUT p_result INT
)
BEGIN
DECLARE v_available INT;
DECLARE v_max_borrow INT;
DECLARE v_current_borrow INT;
DECLARE v_reader_status INT;
START TRANSACTION;
-- 检查读者状态
SELECT account_status, max_borrow INTO v_reader_status, v_max_borrow
FROM reader WHERE reader_id = p_reader_id FOR UPDATE;
IF v_reader_status != 1 THEN
SET p_result = -1; -- 读者账户异常
ROLLBACK;
LEAVE PROCEDURE;
END IF;
-- 检查读者当前借阅量
SELECT COUNT(*) INTO v_current_borrow
FROM borrow_record
WHERE reader_id = p_reader_id AND return_date IS NULL;
IF v_current_borrow >= v_max_borrow THEN
SET p_result = -2; -- 超过最大借阅量
ROLLBACK;
LEAVE PROCEDURE;
END IF;
-- 检查图书可用性
SELECT available_copies INTO v_available
FROM book WHERE book_id = p_book_id FOR UPDATE;
IF v_available <= 0 THEN
SET p_result = -3; -- 图书不可借
ROLLBACK;
LEAVE PROCEDURE;
END IF;
-- 执行借书操作
INSERT INTO borrow_record(book_id, reader_id, borrow_date, due_date, operator_id)
VALUES(p_book_id, p_reader_id, NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY), p_operator_id);
-- 更新图书库存
UPDATE book
SET available_copies = available_copies - 1
WHERE book_id = p_book_id;
SET p_result = 1; -- 借书成功
COMMIT;
END //
DELIMITER ;
4.2 还书事务处理
DELIMITER //
CREATE PROCEDURE sp_return_book(
IN p_record_id BIGINT,
IN p_operator_id VARCHAR(20),
IN p_condition TINYINT, -- 1完好 2损坏 3丢失
OUT p_fine_amount DECIMAL(10,2),
OUT p_result INT
)
BEGIN
DECLARE v_book_id VARCHAR(20);
DECLARE v_due_date DATETIME;
DECLARE v_overdue_days INT;
DECLARE v_lost_fine DECIMAL(10,2);
START TRANSACTION;
-- 获取借阅记录信息
SELECT book_id, due_date INTO v_book_id, v_due_date
FROM borrow_record
WHERE record_id = p_record_id AND return_date IS NULL FOR UPDATE;
IF v_book_id IS NULL THEN
SET p_result = -1; -- 记录不存在或已归还
ROLLBACK;
LEAVE PROCEDURE;
END IF;
-- 计算逾期罚款
SET v_overdue_days = DATEDIFF(NOW(), v_due_date);
IF v_overdue_days > 0 THEN
SET p_fine_amount = LEAST(v_overdue_days * 0.5, 50); -- 每天0.5元,最高50元
ELSE
SET p_fine_amount = 0;
END IF;
-- 处理图书损坏或丢失
IF p_condition = 2 THEN
SET p_fine_amount = p_fine_amount + 20; -- 损坏罚款20元
ELSEIF p_condition = 3 THEN
-- 获取图书价格
SELECT price INTO v_lost_fine FROM book WHERE book_id = v_book_id;
SET p_fine_amount = p_fine_amount + v_lost_fine * 2; -- 丢失按原价2倍赔偿
END IF;
-- 更新借阅记录
UPDATE borrow_record
SET
return_date = NOW(),
status = CASE
WHEN p_condition = 3 THEN 4
WHEN v_overdue_days > 0 THEN 3
ELSE 2
END,
fine_amount = p_fine_amount,
operator_id = p_operator_id
WHERE record_id = p_record_id;
-- 更新图书库存(非丢失情况)
IF p_condition != 3 THEN
UPDATE book
SET available_copies = available_copies + 1
WHERE book_id = v_book_id;
END IF;
SET p_result = 1; -- 还书成功
COMMIT;
END //
DELIMITER ;
5. 索引优化与性能调优
5.1 关键索引设计
除了表结构中已定义的索引外,针对特定查询场景添加以下复合索引:
-- 读者借阅历史查询优化
CREATE INDEX idx_reader_borrow ON borrow_record(reader_id, borrow_date DESC);
-- 图书借阅统计查询优化
CREATE INDEX idx_book_borrow ON borrow_record(book_id, borrow_date);
-- 逾期图书查询优化
CREATE INDEX idx_overdue_books ON borrow_record(status, due_date)
WHERE status IN (1,3) AND return_date IS NULL;
5.2 查询性能优化示例
高频查询1:获取读者当前借阅情况
EXPLAIN ANALYZE
SELECT b.title, b.author, br.borrow_date, br.due_date,
DATEDIFF(br.due_date, CURRENT_DATE()) AS remaining_days
FROM borrow_record br
JOIN book b ON br.book_id = b.book_id
WHERE br.reader_id = 'R20230001' AND br.return_date IS NULL
ORDER BY br.due_date;
优化建议 :确保reader_id和return_date上有索引,使用覆盖索引避免回表。
高频查询2:搜索图书并显示可借状态
EXPLAIN ANALYZE
SELECT b.book_id, b.title, b.author, b.available_copies,
(SELECT COUNT(*) FROM borrow_record br
WHERE br.book_id = b.book_id AND br.return_date IS NULL) AS borrowed_count
FROM book b
WHERE b.title LIKE '%数据库%' OR b.author LIKE '%王珊%'
ORDER BY b.title
LIMIT 20;
优化建议 :对title和author字段建立全文索引,使用MATCH AGAINST替代LIKE:
ALTER TABLE book ADD FULLTEXT INDEX ft_title_author (title, author);
SELECT b.book_id, b.title, b.author, b.available_copies,
(SELECT COUNT(*) FROM borrow_record br
WHERE br.book_id = b.book_id AND br.return_date IS NULL) AS borrowed_count
FROM book b
WHERE MATCH(b.title, b.author) AGAINST('+数据库 +王珊' IN BOOLEAN MODE)
ORDER BY b.title
LIMIT 20;
6. 数据安全与备份策略
6.1 敏感数据加密
对读者身份证号等敏感信息采用AES加密存储:
-- 加密函数
DELIMITER //
CREATE FUNCTION fn_encrypt(p_text VARCHAR(255)) RETURNS VARBINARY(255)
DETERMINISTIC
BEGIN
RETURN AES_ENCRYPT(p_text, 'your-secret-key-123');
END //
DELIMITER ;
-- 解密函数
DELIMITER //
CREATE FUNCTION fn_decrypt(p_cipher VARBINARY(255)) RETURNS VARCHAR(255)
DETERMINISTIC
BEGIN
RETURN AES_DECRYPT(p_cipher, 'your-secret-key-123');
END //
DELIMITER ;
-- 使用示例
UPDATE reader SET id_card = fn_encrypt('110101199003077654');
SELECT name, fn_decrypt(id_card) AS id_card FROM reader;
6.2 定时备份方案
配置MySQL定时全量备份+binlog增量备份:
# 全量备份脚本(每周日)
mysqldump -u root -p your_password library_db \
--single-transaction \
--flush-logs \
--master-data=2 \
--routines \
--triggers \
--events \
> /backup/library_full_$(date +%Y%m%d).sql
# 每日binlog备份
mysqladmin -u root -p flush-logs
cp /var/lib/mysql/mysql-bin.?????? /backup/binlog/
6.3 审计日志表设计
CREATE TABLE audit_log (
log_id BIGINT NOT NULL AUTO_INCREMENT,
user_id VARCHAR(20) COMMENT '操作用户ID',
action VARCHAR(50) NOT NULL COMMENT '操作类型',
table_name VARCHAR(50) NOT NULL COMMENT '操作表名',
record_id VARCHAR(50) COMMENT '记录ID',
old_value JSON COMMENT '旧值',
new_value JSON COMMENT '新值',
ip_address VARCHAR(50) COMMENT 'IP地址',
user_agent VARCHAR(255) COMMENT '用户代理',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (log_id),
KEY idx_action_time (action, create_time),
KEY idx_user_time (user_id, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
7. 高级特性应用
7.1 使用窗口函数生成月度报表
-- 每月图书借阅统计
SELECT
YEAR(borrow_date) AS year,
MONTH(borrow_date) AS month,
COUNT(*) AS borrow_count,
COUNT(DISTINCT reader_id) AS active_readers,
COUNT(DISTINCT book_id) AS unique_books,
SUM(CASE WHEN return_date > due_date THEN 1 ELSE 0 END) AS overdue_count,
ROUND(SUM(CASE WHEN return_date > due_date THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS overdue_rate,
RANK() OVER (ORDER BY COUNT(*) DESC) AS month_rank
FROM borrow_record
GROUP BY YEAR(borrow_date), MONTH(borrow_date)
ORDER BY year DESC, month DESC;
7.2 使用CTE递归查询分类层级
-- 递归查询分类及其所有子分类
WITH RECURSIVE category_tree AS (
-- 基础查询:选择顶级分类
SELECT category_id, category_name, parent_id, category_code, 1 AS level
FROM category
WHERE parent_id IS NULL
UNION ALL
-- 递归查询:连接子分类
SELECT c.category_id, c.category_name, c.parent_id, c.category_code, ct.level + 1
FROM category c
JOIN category_tree ct ON c.parent_id = ct.category_id
)
SELECT
category_id,
CONCAT(REPEAT(' ', level - 1), category_name) AS tree_display,
category_code,
level
FROM category_tree
ORDER BY level, category_name;
7.3 使用生成列自动计算字段
-- 添加虚拟生成列计算图书年龄
ALTER TABLE book ADD COLUMN book_age TINYINT AS (
TIMESTAMPDIFF(YEAR, publish_date, CURRENT_DATE)
) VIRTUAL COMMENT '出版年限';
-- 添加索引优化按年龄段的查询
CREATE INDEX idx_book_age ON book(book_age);
更多推荐

所有评论(0)