设计图书、读者、借阅三张核心表时怎么避免字段冗余
直接用
book_id、
reader_id作主键,别在
borrow_records表里重复存书名或读者姓名。冗余字段会导致更新不一致——比如修改一本书的标题,却漏改借阅记录里的副本,后续查历史借阅就显示旧书名。
正确做法是只存外键:
CREATE TABLE books ( book_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, isbn CHAR(13), author VARCHAR(100), pub_year YEAR ); CREATE TABLE readers ( reader_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, card_no CHAR(10) UNIQUE, reg_date DATE DEFAULT (CURRENT_DATE) ); CREATE TABLE borrow_records ( record_id BIGINT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATE DEFAULT (CURRENT_DATE), return_date DATE NULL, FOREIGN KEY (book_id) REFERENCES books(book_id) ON DELETE RESTRICT, FOREIGN KEY (reader_id) REFERENCES readers(reader_id) ON DELETE RESTRICT );
注意:
ON DELETE RESTRICT防止误删图书或读者导致借阅记录“悬空”;
return_date允许为
NULL,表示尚未归还。
借阅状态用布尔值还是枚举更合适
别用
TINYINT(1)模拟布尔值存“是否已还”,MySQL 的
BOOLEAN实际就是
TINYINT(1)别名,语义弱、扩展差。一旦业务增加“已预约”“已挂失”等状态,就得改字段类型或加新列。
推荐用
ENUM显式定义状态集:
ALTER TABLE borrow_records
ADD COLUMN status ENUM('borrowed', 'returned', 'overdue') DEFAULT 'borrowed';
好处是:数据库层强制校验、查询可读性强(
WHERE status = 'overdue')、应用层不用维护魔法数字映射。但注意
ENUM值变更需
ALTER TABLE,线上表大时要评估锁表影响。
如何快速查某读者当前未还的全部图书
高频查询必须加联合索引,否则每次扫描全表
borrow_records,数据量一过万就明显卡顿。
针对这个查询:
SELECT b.title, br.borrow_date FROM borrow_records br JOIN books b ON br.book_id = b.book_id WHERE br.reader_id = 123 AND br.return_date IS NULL;
应该建索引:
CREATE INDEX idx_reader_borrow ON borrow_records (reader_id, return_date, book_id);
理由:
reader_id是等值条件,放最左;
return_date IS NULL是范围/空值判断,放第二位能利用索引下推;
book_id放最后,用于快速回表查书名,避免二次查询
books表。
MySQL 8.0 以下版本不支持窗口函数,怎么统计每本书被借了多少次
用
GROUP BY+
COUNT(*)最直接:
SELECT b.book_id, b.title, COUNT(br.record_id) AS borrow_times FROM books b LEFT JOIN borrow_records br ON b.book_id = br.book_id AND br.return_date IS NOT NULL GROUP BY b.book_id, b.title ORDER BY borrow_times DESC LIMIT 10;
关键点:
LEFT JOIN保证没被借过的书也出现在结果里(
borrow_times = 0)
AND br.return_date IS NOT NULL放在
ON子句里,不是
WHERE,否则会把未借出的书过滤掉 如果只要“至少被借过一次”的热门书,改用
INNER JOIN并去掉
IS NOT NULL条件即可
真实部署时,
borrow_records表数据增长快,建议按年分区(如按
borrow_dateRANGE 分区),否则单表超千万行后
GROUP BY会变慢。
