简介:一份基于Flask框架实现的图书管理系统源代码,面向数据库课程期中作业或Web开发入门学习者。项目以轻量级Flask为核心,结合数据库存储与Jinja2模板引擎,完整实现图书信息添加、删除、检索等常规功能,并涉及URL路由、视图函数、Flask-WTF表单处理、SQLAlchemy数据模型定义等关键开发环节,可帮助读者快速掌握Flask与数据库结合的应用构建思路,同时理解数据库表结构设计和查询操作。压缩包整体约1.03MB,项目文件主要包含Python源码、模板及静态资源,目录结构分明,便于直接运行和二次扩展。已有355人浏览学习,适合作为课程设计参考或实战练习,在此基础上可继续加入用户认证、权限控制等安全机制,使系统更接近生产环境。
1. 用Flask做图书管理系统:为什么期中作业值得按生产标准做
用Flask实现图书管理系统,听起来是数据库期中作业里的标准配置,但真正动手时你会发现:表结构设计、ORM配置、借阅状态流转,每一个环节都有坑。这篇笔记不打算只给你贴一份能跑的代码,而是把从建库到交作业的完整路径拆开——选什么数据库、表字段怎么定、增删改查怎么写、为什么会遇到MySQL server has gone away这类报错。按生产标准做这份作业有一个直接好处:答辩时能讲出每个设计决策的理由,以后接真实业务也不用推翻重来。适合正要交数据库课程作业、想搭一个内部书籍登记工具、或者刚学Flask想拿真实项目练手的读者。我默认你已经装好了Python和MySQL,命令行能敲mysql -uroot -p进得去。
2. 数据库先行:三张核心表的设计与MySQL建表SQL
2.1 三张核心表:字段设计的原则与冗余取舍
图书管理系统的数据模型,常见的错误是一张表走天下。用户、图书、借阅记录全塞一张表,查询是方便了,但帐面根本对不平。我拆成三张表:users、book、borrow_record。
users表存读者和管理员,字段用username、password_hash、role、created_at。password_hash强调存哈希而不是明文,很多作业里把密码直接存成字符串,老师一眼就能看出来。role用整数,0是管理员、1是普通用户,页面显示时再映射成中文,比直接存"管理员"三个字更利于后面加角色。
book表是核心,除基本书目信息外,我建议保留total_copies和available_copies两个字段。available_copies是冗余字段,因为理论上它可以由借阅记录现场count出来。但图书管理场景并发不高,冗余一个计数字段,列表页就不用每次联表做聚合,响应快很多。代价是借书、还书时必须在一个事务里同时更新它,后面第5章避坑会讲到。
borrow_record表记录每次借还动作,字段是book_id、user_id、borrow_date、due_date、return_date、status。status用0借出、1已还、2逾期三个整数。逾期不落库计算,而是查询时判断:return_date为空且due_date小于今天就是逾期。这样设计能让状态流转只出现在借书和还书两个操作里,逻辑干净。外键我建议都在book_id和user_id上加,删除策略用RESTRICT。有人借了这本书时书就删不掉,正好逼着系统在删除前先做业务检查。
2.2 MySQL建表SQL:直接执行就能用的脚本
CREATE DATABASE IF NOT EXISTS library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library_db; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL, role TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL, title VARCHAR(200) NOT NULL, author VARCHAR(100) NOT NULL, publisher VARCHAR(100), category VARCHAR(50), total_copies INT NOT NULL DEFAULT 1, available_copies INT NOT NULL DEFAULT 1, publish_date DATE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_isbn (isbn), KEY idx_title (title), KEY idx_category (category) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, user_id INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book (id) ON DELETE RESTRICT, CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE RESTRICT, KEY idx_borrow_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段SQL有几个值得注意的地方。第一,建库时直接定utf8mb4而不是utf8,因为MySQL里的utf8最多存3字节,遇到生僻字或表情符号会报错,utf8mb4才是完整UTF-8。第二,book表的isbn加唯一索引,重复录入在数据库层就被拦住,应用层只需要负责给用户提示。第三,借阅记录的外键都带了约束名,后面想改删除策略时用ALTER TABLE ... DROP FOREIGN KEY操作,不用去翻系统表查默认生成的名字。
注意:
users表名避开MySQL里user这个词,因为mysql.user是系统库自带表,业务表叫user容易在各种工具里造成混淆,带个s更稳妥。
测试数据我建议顺手插入三本不同类型的书,让列表页、检索页一打开就有内容,不用调试时对着一张空表发愁。ISBN可以用自己手头的书编一个不冲突的字符串即可。
INSERT INTO book (isbn, title, author, publisher, category, total_copies, available_copies, publish_date) VALUES ('TEST000001', '红楼梦', '曹雪芹', '人民文学出版社', '古典文学', 5, 5, '1996-12-01'), ('TEST000002', '深入理解计算机系统', '兰德尔·E.布莱恩特', '机械工业出版社', '计算机', 3, 2, '2016-11-01'), ('TEST000003', 'Python编程:从入门到实践', '埃里克·马瑟斯', '人民邮电出版社', '计算机', 4, 0, '2020-10-01');注意第三本书available_copies故意设成0,这是"全部借出"状态,调试还书流程时能立刻看出区别。
2.3 ORM还是原生SQL:期中作业选哪条路
建表可以用原生SQL,但业务代码里的增删改查我建议走ORM,具体就是flask-sqlalchemy配pymysql。一是可读性好,一行Book.query.filter_by(category='计算机').all(),答辩时能直接念出来它在问什么;原生SQL拼接容易掺进字符串格式化,一不留神就是SQL注入风险,作业阶段没必要冒险。二是表结构变更成本低,做到一半要加字段,ORM加一行属性定义,配合迁移工具就能搞定;原生SQL得手写ALTER TABLE,再同步改所有INSERT语句里的列名,漏一处就翻车。三是不存在"作业用了ORM显得不高级"的问题,SQLAlchemy同样暴露text()让你执行复杂SQL,只是把常规操作包得更顺手而已。
我习惯先用原生SQL把表建出来,确认字段、索引、外键都没问题,再回头写ORM模型类。这样两边都覆盖到,答辩被问"底层到底执行了什么SQL"时,你能直接答出来。
3. Flask应用骨架:从空白目录到能跑起最小系统
3.1 目录结构与三个前提
先讲清楚前提。Python版本建议3.10以上,太老的环境flask-sqlalchemy依赖解析会有兼容问题。MySQL要能连上,本地3306端口没被占用。第三个前提是虚拟环境,把依赖装进项目自己的venv目录,不要污染全局Python。
目录结构我固定成这样,期中作业规模恰好够用:
library_system/ ├── app/ │ ├── __init__.py │ ├── config.py │ ├── models.py │ ├── views/ │ │ ├── __init__.py │ │ ├── book.py │ │ ├── user.py │ │ └── borrow.py │ ├── templates/ │ └── static/ ├── venv/ ├── run.py └── requirements.txtviews里的三个文件分别对应图书、用户、借阅三个蓝图。拆开的理由是后面加功能时,加文件比在单个app.py里堆路由要轻松得多。你不想答辩时翻开一个800行的app.py,找某个路由要滚半天。
3.2 安装依赖:从零到能import
cd library_system python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install flask flask-sqlalchemy pymysql python-dotenv pip freeze > requirements.txt安装完检查一下pip list里的Flask版本。flask-sqlalchemy对Flask版本有下限要求,旧教程里init_app的返回值写法在3.x里已经变了,如果照着老代码抄,可能拿不到预期对象。requirements.txt是给别人复现用的,交作业默认带一份。
3.3 配置文件:DATABASE_URL里的每个参数都是什么意思
config.py是整个项目最容易被忽略又最值得花时间的地方。我把连接信息放进环境变量,本地开发用.env兜底,部署到服务器时直接改环境变量,不用动代码。
import os from dotenv import load_dotenv load_dotenv() class Config: SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-me') SQLALCHEMY_DATABASE_URI = os.getenv( 'DATABASE_URL', 'mysql+pymysql://root:123456@127.0.0.1:3306/library_db?charset=utf8mb4' ) SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_ENGINE_OPTIONS = { 'pool_size': 5, 'pool_recycle': 3600, 'pool_pre_ping': True }拆开讲每个参数。mysql+pymysql是方言加驱动的写法,以后要连PostgreSQL或SQLite,只需换前缀,业务代码不用动。连接串里密码若有特殊字符,比如@,要先做URL编码,否则SQLAlchemy解析时会在错误位置截断。charset=utf8mb4必须带,这和第2章建库时指定的字符集呼应,三层字符集少一层都会乱码。
pool_recycle=3600是让连接池里的连接每3600秒强制重建一次,配合MySQL默认的wait_timeout,能避免"MySQL server has gone away"这类报错。pool_pre_ping=True则是每次取连接前先ping一下,失效连接直接丢弃换新的。这两个参数是血泪经验换来的,作业阶段加上能少踩一半坑。
3.4 应用工厂与蓝图:为什么把路由拆开
app/__init__.py里用应用工厂模式创建Flask实例。理由只有一个:方便测试。后面写冒烟测试时,每次create_app()都能拿到一个全新配置的应用,测试之间不会互相污染。
from flask import Flask from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def create_app(): app = Flask(__name__) app.config.from_object('app.config.Config') db.init_app(app) from app.views.book import bp as book_bp from app.views.user import bp as user_bp from app.views.borrow import bp as borrow_bp app.register_blueprint(book_bp) app.register_blueprint(user_bp) app.register_blueprint(borrow_bp) return app蓝图文件里的标准写法是:
from flask import Blueprint, render_template bp = Blueprint('book', __name__, url_prefix='/books') @bp.route('/') def index(): return render_template('book_list.html')url_prefix='/books'的意思是,这个蓝图里的所有路由都自动带/books前缀,书相关的增删改查全部从这里进出。run.py放在项目根目录,是最外层的启动入口:
from app import create_app app = create_app() if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)开发阶段开着debug=True,改完代码自动重载,不用手动重启,这对赶作业特别友好。部署到服务器上时你不会用app.run,而是用gunicorn拉起这个create_app()返回的实例,但本地开发和验收用app.run就够了。上线前debug=True一定关掉,否则访问者能看到完整堆栈和敏感信息。
4. 图书增删改查:ORM怎么用才能省一半代码
4.1 模型类定义:字段类型、默认值与关系
models.py里把三张表翻译成Python类。字段类型要和建表SQL严格对齐:DATETIME用db.DateTime,DATE用db.Date,TINYINT用db.SmallInteger。对齐不一致时最常见的症状是日期字段读出来是字符串,模板里没法直接调用strftime。
from app import db from datetime import datetime class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), unique=True, nullable=False) password_hash = db.Column(db.String(255), nullable=False) role = db.Column(db.SmallInteger, default=1, nullable=False) created_at = db.Column(db.DateTime, default=datetime.now) class Book(db.Model): __tablename__ = 'book' id = db.Column(db.Integer, primary_key=True) isbn = db.Column(db.String(20), unique=True, nullable=False) title = db.Column(db.String(200), nullable=False) author = db.Column(db.String(100), nullable=False) publisher = db.Column(db.String(100)) category = db.Column(db.String(50)) total_copies = db.Column(db.Integer, default=1, nullable=False) available_copies = db.Column(db.Integer, default=1, nullable=False) publish_date = db.Column(db.Date) created_at = db.Column(db.DateTime, default=datetime.now) def __repr__(self): return f'<Book {self.isbn} {self.title}>' class BorrowRecord(db.Model): __tablename__ = 'borrow_record' id = db.Column(db.Integer, primary_key=True) book_id = db.Column(db.Integer, db.ForeignKey('book.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) borrow_date = db.Column(db.Date, nullable=False) due_date = db.Column(db.Date, nullable=False) return_date = db.Column(db.Date) status = db.Column(db.SmallInteger, default=0, nullable=False) book = db.relationship('Book', backref=db.backref('borrow_records', lazy='dynamic')) user = db.relationship('User', backref=db.backref('borrow_records', lazy='dynamic'))relationship是ORM相比原生SQL最舒服的地方。查出一条借阅记录后,直接取record.book.title就能拿到书名,不用手写JOIN。lazy='dynamic'让反向引用变成一个查询对象,可以继续调用.filter(),而不是一访问就把所有记录灌进内存。
4.2 图书列表页:分页、模糊检索和关键字回显
图书列表页是系统门面,一上来要解决三件事:分页、关键字搜索、搜索词回显。分页用flask-sqlalchemy自带的paginate,不用手动算offset。
from flask import Blueprint, render_template, request from app.models import Book from app import db bp = Blueprint('book', __name__, url_prefix='/books') @bp.route('/') def book_list(): page = request.args.get('page', 1, type=int) keyword = request.args.get('keyword', '', type=str).strip() query = Book.query if keyword: like_pattern = f'%{keyword}%' query = query.filter( db.or_( Book.title.like(like_pattern), Book.author.like(like_pattern), Book.isbn.like(like_pattern) ) ) pagination = query.order_by(Book.created_at.desc()).paginate( page=page, per_page=10, error_out=False ) return render_template( 'book_list.html', pagination=pagination, keyword=keyword )paginate三个参数值得记:page是当前页码,per_page是每页条数,error_out=False表示页码超出范围时返回空列表而不是抛404。模板里渲染分页栏用pagination.pages判断总页数,用has_prev和has_next控制上一页下一页按钮。关键字回显就是把keyword原样传回模板,放在输入框的value里,翻页后搜索条件不丢。
模糊检索用like是最省方案的写法,数据量到几万条时性能会下降,但期中作业完全够用。真要到全文检索阶段,再考虑MySQL全文索引或上ES,现在别过度设计。
4.3 新增与编辑表单:ISBN查重和日期解析
新增图书的表单处理逻辑,核心是防重复和数据清洗。前端HTML的必填校验是给用户友好提示,真正拦住脏数据的是后端这一段。
from datetime import datetime from flask import request, redirect, url_for, flash def parse_date(value): if not value: return None try: return datetime.strptime(value, '%Y-%m-%d').date() except ValueError: return None @bp.route('/add', methods=['GET', 'POST']) def book_add(): if request.method == 'POST': isbn = request.form.get('isbn', '').strip() title = request.form.get('title', '').strip() author = request.form.get('author', '').strip() if not isbn or not title or not author: flash('ISBN、书名、作者不能为空') return redirect(url_for('book.book_add')) exists = Book.query.filter_by(isbn=isbn).first() if exists: flash(f'ISBN {isbn} 已存在,请检查是否重复录入') return redirect(url_for('book.book_add')) total = int(request.form.get('total_copies', 1) or 1) book = Book( isbn=isbn, title=title, author=author, publisher=request.form.get('publisher', '').strip(), category=request.form.get('category', '').strip(), total_copies=total, available_copies=total, publish_date=parse_date(request.form.get('publish_date', '')) ) db.session.add(book) db.session.commit() flash(f'《{title}》添加成功') return redirect(url_for('book.book_list')) return render_template('book_form.html')两个细节说一下。isbn查重用filter_by精确匹配,用first()而不是all(),因为只需要知道有没有。total_copies和available_copies同时赋值为total,这是新书刚入库的必然状态。新手容易漏写available_copies,然后列表页显示有书却一本都借不出去。
表单里的日期字段,HTML5的date输入会给YYYY-MM-DD字符串,但用户可能手输别的格式。parse_date兜底解析,失败返回None而不是让页面直接抛异常。这个函数建议全项目共用,编辑时还会再遇到一次。
4.4 删除必须用POST表单:拒绝GET副作用
删除图书我坚持只用POST,而且是带CSRF令牌的POST。原因是GET请求会被浏览器预加载、爬虫、甚至聊天工具里的链接预览触发。如果删除接口写成/books/3/delete这样的GET,用户分享一个链接给别人,别人点开就把书删了。这种事故在真实项目里发生过太多次。
@bp.route('/<int:book_id>/delete', methods=['POST']) def book_delete(book_id): book = db.get_or_404(Book, book_id) if book.available_copies < book.total_copies: flash('这本书还有未归还的借阅记录,不能删除') return redirect(url_for('book.book_list')) db.session.delete(book) db.session.commit() flash(f'《{book.title}》已删除') return redirect(url_for('book.book_list'))db.get_or_404是flask-sqlalchemy的语法糖,取不到记录直接返回404,比先query再判None少两行。删除前检查available_copies < total_copies,是第2章外键RESTRICT约束的业务层补充。数据库层拦住了"借出中的书还能删",业务层则给用户一个明确提示而不是抛500错误。两层都写,答辩时能讲出完整的设计思路。
4.5 用户登录与密码哈希:作业里最容易被扣分的地方
用户模块很容易被做成摆设,但密码这件事不能省。期中作业里见过太多password字段直接存明文,这是答辩时老师一眼能看出的硬伤。用werkzeug自带的哈希函数,几行代码解决。
from werkzeug.security import generate_password_hash, check_password_hash from flask import session, flash, redirect, url_for @bp.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '') user = User.query.filter_by(username=username).first() if not user or not check_password_hash(user.password_hash, password): flash('用户名或密码错误') return redirect(url_for('user.login')) session['user_id'] = user.id session['username'] = user.username return redirect(url_for('book.book_list')) return render_template('login.html')注册时用generate_password_hash(password)存储,登录时用check_password_hash比对。session是Flask自带的会话机制,默认签名存在浏览器Cookie里,配合第3章那个SECRET_KEY做防篡改。注意SECRET_KEY千万别用默认值上线,否则别人可以伪造会话。
5. Flask+MySQL的5个典型踩坑:现象、原因、解法
5.1 中文乱码满天飞
现象:页面显示一堆问号或者"鏄"这样的乱码,数据库里也存不进中文。原因:三层字符集至少有一层不是utf8mb4。第一层是MySQL服务端的character_set_server,第二层是建库时指定的字符集,第三层是pymysql连接串里的charset参数。最常见的是建库时用了默认latin1,或者连接串漏了charset=utf8mb4。
解决:把第2章的建库语句改成DEFAULT CHARACTER SET utf8mb4,连接串里补上?charset=utf8mb4。改完之后重新插入中文数据。已经乱掉的数据没有后悔药,只能删库重建,所以一开始就定好字符集比事后补救重要。检查当前状态用SHOW VARIABLES LIKE 'character_set%';,这是排查MySQL字符集问题最常用的命令。
5.2 MySQL server has gone away
现象:开发时一切正常,挂了一晚上,第二天刷新页面突然报OperationalError: (2006, 'MySQL server has gone away')。重启Flask应用又好了,过一阵又复发。原因:MySQL默认wait_timeout是8小时,连接池里的连接超过这个时间没活动,服务端主动断开。SQLAlchemy连接池不知情,仍然把旧连接拿出来用,于是客户端才发现对端早已关闭。这是Flask开发模式下最典型的"玄学"问题。
解决:config.py里的SQLALCHEMY_ENGINE_OPTIONS加pool_recycle=3600和pool_pre_ping=True。pool_recycle让连接在服务端超时前被回收重建,pool_pre_ping每次取连接前做一次轻量ping,失效连接直接丢弃。这两个参数加上,基本整段开发期都不会再碰到这个报错。
5.3 外键约束导致删除失败
现象:删除一本被借走的书,页面不报错,但数据库弹出foreign key constraint fails,或者ORM抛出IntegrityError。原因:建表时外键用了ON DELETE RESTRICT,这是有意为之。借阅记录表里还有引用这本book_id的记录,数据库层不允许级联删除。如果业务层没写检查,这个报错就会原样抛到页面上。
解决:业务层先判断available_copies < total_copies再删,给出友好提示,这是第4章4.4已经写过的逻辑。如果确实想强制删除,可以先把关联的borrow_record记录清掉再删书,但真实场景中这属于高危操作,不建议。保持RESTRICT约束,让系统记住"有借阅历史的书不能删",反而是保护数据完整性的正确选择。
5.4 列表页越查越慢:N+1查询
现象:图书列表页每页只有10条,响应却要几百毫秒。打开SQL日志发现每次渲染要执行几十条SQL,其中大部分是重复的联表查询。原因:模板里对每条记录访问record.book.title时,ORM会懒加载关联对象。10条记录就是10次额外查询,加上主查询就是11次。如果页面上还显示借阅人、借阅日期,N+1问题被进一步放大。数据量小的时候感觉不到,数据一多就翻车。
解决:查询时用joinedload预加载关联表。借阅记录列表页写成BorrowRecord.query.options(joinedload(BorrowRecord.book), joinedload(BorrowRecord.user)),一条SQL用LEFT JOIN把两张关联表一起查出来,ORM再组装对象。检查有没有N+1,临时把SQLALCHEMY_ECHO设为True,控制台会打印每条SQL,循环里的重复查询立刻现形。
5.5 GET删除和浏览器预加载
现象:用户反馈"我只是收藏了个链接,书就没了",或者爬虫访问了一轮,数据被删掉一批。原因:删除接口用了GET方法,URL形如/books/3/delete。GET请求可以被任何地方触发:链接预读、浏览器主动预取、聊天工具里的链接预览。只要请求带够参数,就会执行删除。
解决:改成第4章4.4那样的POST表单,模板里用<form method="post">提交。更进一步用flask-wtf加CSRF令牌,防止跨站请求伪造。期中作业阶段至少做到POST,答辩时能说出"GET请求不应产生副作用,这是HTTP规范的要求"这句话,印象分会高不少。
6. 交作业前最后一步:冒烟测试、备份与三个进阶方向
6.1 冒烟测试
测试不监听端口,直接用Flask的测试客户端模拟HTTP请求,跑起来比手动点页面快得多。准备三个用例:列表页可达、新增页可达、删除不存在的书返回404。
import pytest from app import create_app @pytest.fixture() def client(): app = create_app() app.config['TESTING'] = True with app.test_client() as client: yield client def test_book_list_ok(client): resp = client.get('/books') assert resp.status_code == 200 def test_book_add_page_ok(client): resp = client.get('/books/add') assert resp.status_code == 200 def test_book_delete_missing_returns_404(client): resp = client.post('/books/99999/delete') assert resp.status_code == 404跑pytest之前先pip install pytest,然后直接命令行执行。这三个用例能拦住大多数低级错误:路由写错、模板找不到、视图函数崩了。
6.2 备份与恢复
交作业前用mysqldump导一份SQL存档,代码改崩了也能回到稳定状态。
mysqldump -uroot -p library_db > library_db_backup.sql mysql -uroot -p library_db < library_db_backup.sql导出的文件是纯SQL,可以直接给老师演示数据模型,比截图有说服力。恢复命令同样简单,注意执行前确认目标库是空的,否则会往里追加数据。
6.3 三个进阶方向
第一个是把视图函数改成返回JSON,前端用fetch渲染,为前后端分离做准备,等以后拿FastAPI做对比时会有更具体的体会。第二个是用Flask-Migrate管理表结构迁移,以后再也不用手动ALTER TABLE。第三个是在检索接口上接一层Redis,缓存热门查询词,这个优化能把响应时间从几十毫秒压到个位数。
我还记得第一次做这类系统时,连接池参数完全没有概念,大半夜被"MySQL server has gone away"折腾到怀疑人生。后来把pool_recycle和pool_pre_ping加进配置,这套组合几乎成了我所有Flask+MySQL项目的默认配置。希望这份笔记能帮你少踩几个同样的坑,把图书管理系统做得扎实一点,顺利交掉这份作业。
本文还有配套的精品资源,点击获取