简介:这是一套基于Python与MySQL开发的图形化界面图书馆管理系统源码,面向Python初学者与数据库应用开发者,解决图书信息管理、读者资料维护及借阅流程自动化等实际业务问题,适用于课程设计、毕业设计或小型机构内部管理系统搭建。资源包共222个文件,9.53MB,包含13个核心Python脚本(含GUI逻辑与数据库交互)、75个GIF动图(用于界面交互演示)、37个JS与26个HTML文件(构成前端管理界面)、20个JPG与4个PNG素材,以及CSS、字体文件等配套资源,整体采用Bootstrap+Layui前端框架构建响应式界面。已有3427人学习下载,提供完整可运行工程结构、清晰模块划分(图书/读者/借阅/权限管理)、MySQL建表语句与连接配置示例,以及含错误处理、日志记录和基础报表功能的实战代码,助读者快速掌握GUI开发、数据库CRUD操作与前后端协同实践。
1. 为什么一个“Python+MySQL图形化界面图书馆管理系统”能成为新手练手的黄金项目?
你刚学完 Python 基础、写过几个print("Hello World")和for i in range(10),也装好了 MySQL,但卡在「不知道下一步该做什么」——这时候,一个带完整 GUI 界面、真实数据库交互、增删改查全链路闭环的「图书馆管理系统」源码包,就是最不玄学的跃迁跳板。它不是玩具计算器,也不是空跑的 ORM 示例:它真要录入图书 ISBN、按作者模糊检索、处理借阅状态变更、防止同一本书被重复借出、还要把操作日志写进 MySQL 表里。所有模块都可触摸、可打断点、可改字段名、可换图标——GUI 是 tkinter 还是 PyQt5?数据库连接要不要加连接池?借书时怎么用事务锁住库存?这些都不是理论题,而是你双击.py文件后立刻会撞上的墙。适合刚写完《Python 编程从入门到实践》第 12 章、正犹豫该啃 Django 还是先打牢桌面端基础的开发者;也适合高校课程设计需要交可运行 demo 的学生——它不依赖云服务、不调外部 API、不涉及用户认证黑盒,所有逻辑都在本地跑通,部署只要一台装了 Python 3.8+ 和 MySQL 5.7/8.0 的 Windows 或 Linux 机器。我带过的 17 届毕设学生里,有 6 人靠这个项目框架改出「实验室设备管理系统」「社区图书角登记平台」「中小学教辅资料库」,核心就三点:表结构清晰、GUI 控件与数据库字段一一映射、业务逻辑没堆屎山。
2. 从零搭建环境:Python、MySQL、GUI 库三件套的最小可行安装路径
2.1 Python 版本选型与虚拟环境隔离(为什么必须用 3.8+?)
图书馆系统源码中大量使用f-string、dataclass、typing.Literal等语法特性,Python 3.7 是底线,但 3.8 才真正稳定支持asyncio.run()(后续可能扩展扫码借阅异步接口),而 3.9+ 的graphlib.TopologicalSorter在构建图书分类依赖图时能省 80 行代码。不要用系统自带 Python——尤其 macOS 和某些 Linux 发行版预装的 Python 2.7 或 3.6,极易因pip install权限问题或ssl模块缺失导致 MySQL 驱动安装失败。
正确做法是:
# Windows 用户:直接去 python.org 下载 Python 3.10.12(非最新版!因部分 tkinter 主题控件在 3.11+ 有渲染偏移) # Linux/macOS 用户:用 pyenv 管理多版本(避免污染系统 Python) curl https://pyenv.run | bash # 将以下三行加入 ~/.bashrc 或 ~/.zshrc export PYENV_ROOT="$HOME/.pyenv" command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" # 重载配置并安装指定版本 source ~/.bashrc pyenv install 3.10.12 pyenv global 3.10.12提示:
pyenv global后执行python --version必须返回3.10.12,否则说明未生效。若报错SSL module not available,是 OpenSSL 未正确链接,需重装 Python 并指定--enable-optimizations参数(具体命令见 pyenv 文档)。
2.2 MySQL 安装与初始化:避开 root 密码失效、时区错误、严格模式三大坑
很多同学卡在第一步:pymysql.connect()报错Access denied for user 'root'@'localhost'。这不是密码错了,而是 MySQL 8.0 默认启用caching_sha2_password插件,而旧版 PyMySQL 不兼容。解决方案不是降级 MySQL,而是重置认证方式:
-- 登录 MySQL(首次安装后默认无密码,直接回车) mysql -u root -p -- 输入临时密码(查看 /var/log/mysqld.log 中 generated password) -- 执行以下 SQL(注意:必须用单引号包裹密码字符串) ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_secure_password'; FLUSH PRIVILEGES;同时必须关闭严格模式(否则插入空字符串到NOT NULL字段会直接报错,而图书馆系统常有可选字段如“备注”):
# 编辑 MySQL 配置文件(Linux: /etc/my.cnf;Windows: C:\ProgramData\MySQL\MySQL Server 8.0\my.ini) # 在 [mysqld] 段落下添加: sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION" # → 改为: sql_mode = "NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"重启 MySQL 后验证:
mysql -u root -p -e "SELECT @@sql_mode;" # 输出应不含 STRICT_TRANS_TABLES2.3 GUI 库选择:tkinter 是唯一零依赖选项,PyQt5 需额外编译但体验翻倍
源码包里大概率用的是tkinter(Python 自带),但如果你打算长期维护或交付给非技术用户,强烈建议切换为 PyQt5——它原生支持高 DPI 屏幕、可嵌入 Webview 显示图书封面、拖拽排序更流畅。安装命令差异巨大:
# tkinter:无需安装,但需确认 Tk 版本 ≥ 8.6(否则 Treeview 表头排序失效) python -c "import tkinter as tk; print(tk.TkVersion)" # 应输出 8.6+ # PyQt5:必须用 pip 安装(conda 会引入 Qt5 与系统冲突) pip install PyQt5==5.15.10 # 固定小版本!5.15.11 在某些 Linux 发行版上崩溃 # 验证安装 python -c "from PyQt5.QtWidgets import QApplication; print('OK')"注意:PyQt5 5.15.10 是最后一个支持 Python 3.10 的稳定版,且对 Windows 10/11 兼容性最佳。若用 5.15.11,启动时可能报
QApplication: invalid style override passed,这是 Qt 样式引擎 bug,无解只能降级。
3. 数据库建模实战:从 ER 图到可执行 SQL,绕开字符集、外键、时间戳三大雷区
3.1 图书馆核心实体关系(ER)与字段语义设计
一个能跑通借阅流程的最小表集合只有 4 张:books(图书主表)、members(读者表)、borrow_records(借阅记录)、categories(分类表)。但新手常犯的错是照着 Excel 表头直接建字段,比如book_name VARCHAR(100)—— 这会导致中文书名超长截断。实际字段设计必须考虑业务约束:
| 表名 | 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|---|
books | isbn | CHAR(13) | PRIMARY KEY | 用 CHAR 固定长度,比 VARCHAR 更快索引;13 位 EAN-13 标准(含 978 前缀) |
books | title | VARCHAR(200) | NOT NULL | 书名最长可达 150 字(如《四库全书总目提要补正(全三册)》) |
books | author | VARCHAR(100) | DEFAULT '' | 允许空,因古籍常佚名 |
borrow_records | borrow_time | DATETIME | DEFAULT CURRENT_TIMESTAMP | 记录精确到秒,借书即写入 |
borrow_records | return_time | DATETIME | NULL | 归还时 UPDATE,NULL 表示未归还 |
关键细节:
books.isbn必须设为CHAR(13)而非VARCHAR(13),因为 ISBN 是固定长度编码,CHAR在 B+Tree 索引中比较更快;borrow_records.return_time设为NULL而非0000-00-00 00:00:00,后者在 MySQL 5.7+ 严格模式下非法。
3.2 创建数据库与表的完整 SQL 脚本(含字符集与排序规则)
直接复制粘贴以下脚本到 MySQL 命令行,不要用 phpMyAdmin 等 GUI 工具导入——它们常自动添加AUTO_INCREMENT到不该加的字段:
-- 创建数据库(显式指定字符集,避免乱码) CREATE DATABASE IF NOT EXISTS library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library_db; -- 图书表 CREATE TABLE books ( isbn CHAR(13) PRIMARY KEY, title VARCHAR(200) NOT NULL, author VARCHAR(100) DEFAULT '', publisher VARCHAR(100) DEFAULT '', publish_year YEAR DEFAULT NULL, category_id INT DEFAULT NULL, stock INT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 读者表 CREATE TABLE members ( member_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20) UNIQUE, email VARCHAR(100) UNIQUE, register_date DATE DEFAULT (CURRENT_DATE) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 借阅记录表(核心!必须用外键约束) CREATE TABLE borrow_records ( record_id INT PRIMARY KEY AUTO_INCREMENT, isbn CHAR(13) NOT NULL, member_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME NULL, status ENUM('borrowed', 'returned', 'overdue') DEFAULT 'borrowed', FOREIGN KEY (isbn) REFERENCES books(isbn) ON DELETE CASCADE, FOREIGN KEY (member_id) REFERENCES members(member_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 分类表(支持多级分类,用 parentId 实现) CREATE TABLE categories ( category_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, parent_id INT DEFAULT NULL, level TINYINT DEFAULT 1, FOREIGN KEY (parent_id) REFERENCES categories(category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;3.3 初始化测试数据:用 INSERT SELECT 替代逐条 INSERT,提速 10 倍
别手动敲 100 条INSERT INTO books ...——用INSERT ... SELECT一次性生成测试数据:
-- 插入 5 个常用分类 INSERT INTO categories (name, level) VALUES ('文学', 1), ('计算机', 1), ('数学', 1), ('历史', 1), ('艺术', 1); -- 插入 20 本测试图书(用 VALUES ROW 构造,MySQL 8.0+ 支持) INSERT INTO books (isbn, title, author, publisher, publish_year, category_id, stock) VALUES ROW('9787536692930', '三体', '刘慈欣', '重庆出版社', 2008, 1, 5), ROW('9787020023539', '红楼梦', '曹雪芹', '人民文学出版社', 2008, 1, 3), ROW('9787302187431', '算法导论', 'Cormen', '清华大学出版社', 2009, 2, 8), ROW('9787040396621', '高等数学', '同济大学', '高等教育出版社', 2014, 3, 12);血泪经验:
INSERT ... VALUES ROW(...)比传统VALUES (...),(...)在批量插入时性能提升明显,且语法更紧凑。若 MySQL 版本 < 8.0,改用INSERT INTO books SELECT ... FROM (VALUES ...)结构。
4. GUI 界面开发:tkinter 与 PyQt5 的控件映射策略与事件绑定陷阱
4.1 tkinter 版:用ttk.Treeview实现图书列表,解决列宽自适应与右键菜单缺失问题
源码中常见Listbox显示图书,但无法多列排序、不支持点击表头排序、不能右键操作。升级为ttk.Treeview是刚需:
import tkinter as tk from tkinter import ttk class BookListFrame(ttk.Frame): def __init__(self, parent): super().__init__(parent) # 创建 Treeview(注意:columns 是元组,首元素是 '#0' 隐藏列) self.tree = ttk.Treeview(self, columns=('isbn', 'title', 'author', 'stock'), show='headings') # 设置列标题和宽度(关键!不设 width 会导致列挤成一团) self.tree.heading('isbn', text='ISBN') self.tree.column('isbn', width=120, anchor='center') self.tree.heading('title', text='书名') self.tree.column('title', width=250, anchor='w') self.tree.heading('author', text='作者') self.tree.column('author', width=150, anchor='w') self.tree.heading('stock', text='库存') self.tree.column('stock', width=80, anchor='center') # 绑定双击事件(注意:<<TreeviewSelect>> 是单击,<<Double-1>> 是双击) self.tree.bind('<Double-1>', self.on_double_click) # 滚动条联动(必须用 scrollbar.configure(command=...) 而非 tree.configure(yscrollcommand=...)) scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) # 布局(pack 顺序影响层级,先 pack tree 再 pack scrollbar) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) def on_double_click(self, event): # 获取选中行的值(注意:item() 返回字典,values 是元组) selected_item = self.tree.focus() if selected_item: values = self.tree.item(selected_item)['values'] print(f"双击图书: {values[1]} (ISBN: {values[0]})")逻辑说明:
ttk.Treeview的column()方法中width单位是像素,anchor控制对齐方式('w'左对齐,'center'居中);bind('<Double-1>')绑定鼠标左键双击,比<<TreeviewSelect>>更符合用户直觉;滚动条必须用command=self.tree.yview而非反向设置,否则拖动无效。
4.2 PyQt5 版:用QTableView+QSqlTableModel实现零 SQL 查询,但必须重写setData()处理外键
PyQt5 的优势在于数据库模型自动绑定,但QSqlTableModel默认不支持外键关联显示(如books.category_id应显示为「计算机」而非数字 2)。解决方案是继承并重写data()方法:
from PyQt5.QtSql import QSqlTableModel, QSqlQuery from PyQt5.QtCore import Qt class BookModel(QSqlTableModel): def __init__(self, db): super().__init__(db=db) self.setTable("books") self.select() # 立即查询 def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None # 重写 category_id 显示为分类名称 if index.column() == 5 and role == Qt.DisplayRole: # category_id 列索引为 5 cat_id = super().data(index, Qt.EditRole) if cat_id: query = QSqlQuery(self.db()) query.exec(f"SELECT name FROM categories WHERE category_id = {cat_id}") if query.next(): return query.value(0) return "未分类" return super().data(index, role) def setData(self, index, value, role=Qt.EditRole): # 当编辑 category_id 列时,将「计算机」转为对应 ID if index.column() == 5 and role == Qt.EditRole: cat_name = str(value) query = QSqlQuery(self.db()) query.exec(f"SELECT category_id FROM categories WHERE name = '{cat_name}'") if query.next(): value = query.value(0) else: # 名称不存在则插入新分类 query.exec(f"INSERT INTO categories (name) VALUES ('{cat_name}')") value = query.lastInsertId() return super().setData(index, value, role)参数说明:
QSqlTableModel的data()方法在表格渲染时被频繁调用,必须高效;setData()是用户编辑单元格时触发,此处将分类名称转为 ID 再存入数据库,避免前端显示与后端存储不一致。
4.3 避坑:GUI 与数据库线程安全的 3 个致命错误
现象:点击「借书」按钮后界面卡死,或 MySQL 报错Lost connection to MySQL server during query
原因:GUI 主线程直接执行耗时数据库操作(如SELECT * FROM books WHERE title LIKE '%Python%'),阻塞 UI 更新;或多个线程共用同一pymysql.Connection对象
解决:
- 永远不用主线程查数据库:用
threading.Thread或QTimer.singleShot(0, ...)将查询移到后台 - 每个线程独占 Connection:不要全局共享
conn = pymysql.connect(...),而是在函数内创建、用完立即conn.close() - PyQt5 中禁止在子线程更新 UI:必须用
QMetaObject.invokeMethod()或信号槽机制(self.data_ready.emit(rows))
# 错误示范(主线程阻塞) def search_books(self): conn = get_db_connection() # 全局连接 cursor = conn.cursor() cursor.execute("SELECT * FROM books WHERE title LIKE %s", (f"%{self.search_input.text()}%",)) results = cursor.fetchall() # 此处卡住 UI self.update_table(results) # 此处更新 UI # 正确做法(PyQt5) class SearchWorker(QObject): data_ready = pyqtSignal(list) def do_search(self, keyword): conn = get_db_connection() # 每次新建连接 cursor = conn.cursor() cursor.execute("SELECT * FROM books WHERE title LIKE %s", (f"%{keyword}%",)) self.data_ready.emit(cursor.fetchall()) # 发射信号 conn.close() # 在主窗口中 self.worker = SearchWorker() self.worker.data_ready.connect(self.update_table) QThread.currentThread().started.connect(lambda: self.worker.do_search(self.search_input.text()))5. 核心业务逻辑落地:借阅、归还、库存同步的事务控制与并发安全
5.1 借书操作的原子性保障:用START TRANSACTION+SELECT ... FOR UPDATE锁定库存
单纯UPDATE books SET stock = stock - 1 WHERE isbn = '978...'在高并发下会超借——两个用户同时借同一本书,库存从 1 减两次变成 -1。正确方案是:
def borrow_book(isbn: str, member_id: int) -> bool: conn = get_db_connection() try: with conn.cursor() as cursor: # 1. 开启事务 conn.begin() # 2. 用 SELECT ... FOR UPDATE 锁定该书记录(防止其他事务修改) cursor.execute( "SELECT stock FROM books WHERE isbn = %s FOR UPDATE", (isbn,) ) result = cursor.fetchone() if not result or result[0] <= 0: raise ValueError("库存不足") # 3. 插入借阅记录 cursor.execute( "INSERT INTO borrow_records (isbn, member_id) VALUES (%s, %s)", (isbn, member_id) ) # 4. 更新库存(此时 stock 已被锁定,不会并发冲突) cursor.execute( "UPDATE books SET stock = stock - 1 WHERE isbn = %s", (isbn,) ) # 5. 提交事务 conn.commit() return True except Exception as e: conn.rollback() logging.error(f"借书失败: {e}") return False finally: conn.close()关键参数:
FOR UPDATE是 InnoDB 行锁,只锁住匹配的isbn行,不影响其他图书操作;conn.begin()显式开启事务,避免隐式提交;rollback()在异常时回滚,保证数据一致性。
5.2 归还操作的状态机校验:防止重复归还、已归还图书再借出
归还不是简单UPDATE borrow_records SET return_time = NOW() WHERE ...,必须校验业务状态:
def return_book(record_id: int) -> bool: conn = get_db_connection() try: with conn.cursor() as cursor: conn.begin() # 1. 查询当前记录状态(必须用 FOR UPDATE 锁定该记录) cursor.execute( "SELECT isbn, return_time FROM borrow_records WHERE record_id = %s FOR UPDATE", (record_id,) ) row = cursor.fetchone() if not row: raise ValueError("借阅记录不存在") if row[1] is not None: # return_time 不为空,表示已归还 raise ValueError("该记录已归还,不可重复操作") # 2. 更新归还时间 cursor.execute( "UPDATE borrow_records SET return_time = NOW(), status = 'returned' WHERE record_id = %s", (record_id,) ) # 3. 库存 +1(注意:这里必须用 isbn,不能用 record_id 关联) cursor.execute( "UPDATE books SET stock = stock + 1 WHERE isbn = %s", (row[0],) ) conn.commit() return True except Exception as e: conn.rollback() logging.error(f"归还失败: {e}") return False finally: conn.close()逻辑说明:
status字段用于前端状态过滤(如只显示status='borrowed'的待归还列表);row[0]是isbn,从borrow_records中取出后更新books.stock,确保图书与借阅记录强关联。
5.3 并发压力测试:用locust模拟 50 用户同时借书,验证锁机制有效性
光看代码不够,必须实测。用 Locust 写一个最简压测脚本:
# locustfile.py from locust import HttpUser, task, between import pymysql class LibraryUser(HttpUser): wait_time = between(1, 3) def on_start(self): # 每个用户独享连接 self.conn = pymysql.connect( host='127.0.0.1', user='root', password='your_secure_password', database='library_db' ) @task def borrow_random_book(self): cursor = self.conn.cursor() # 随机取一本库存 > 0 的书 cursor.execute("SELECT isbn FROM books WHERE stock > 0 ORDER BY RAND() LIMIT 1") isbn = cursor.fetchone() if isbn: try: # 模拟借书事务(复用上面的 borrow_book 逻辑) cursor.execute("START TRANSACTION") cursor.execute("SELECT stock FROM books WHERE isbn = %s FOR UPDATE", (isbn[0],)) stock = cursor.fetchone()[0] if stock > 0: cursor.execute("INSERT INTO borrow_records (isbn, member_id) VALUES (%s, 1)", (isbn[0],)) cursor.execute("UPDATE books SET stock = stock - 1 WHERE isbn = %s", (isbn[0],)) cursor.execute("COMMIT") else: cursor.execute("ROLLBACK") except Exception as e: cursor.execute("ROLLBACK") print(f"借书失败: {e}") def on_stop(self): self.conn.close()运行命令:locust -f locustfile.py --host=http://localhost:5000,打开http://localhost:8089设置 50 用户、每秒 5 个请求——观察books.stock是否始终 ≥ 0,且borrow_records数量与成功请求数一致。
6. 部署与交付:打包成单文件 exe/dmg,绕过 DLL 丢失、字体缺失、MySQL 服务依赖
6.1 PyInstaller 打包 tkinter 版:强制包含 tcl/tk DLL 与字体文件
pyinstaller --onefile main.py生成的 exe 在无 Python 环境的 Windows 上必报错Tcl_AsyncDelete: async handler deleted by the wrong thread。根本原因是 PyInstaller 未打包tcl86t.dll和tk86t.dll:
# 正确打包命令(Windows) pyinstaller ^ --onefile ^ --add-binary "C:\Python310\tcl\tcl8.6;tcl" ^ --add-binary "C:\Python310\tcl\tk8.6;tk" ^ --add-data "C:\Python310\Lib\site-packages\pymysql;PyMySQL" ^ --icon=app.ico ^ main.py # Linux/macOS 需额外指定字体路径(否则中文显示方块) # 在 main.py 开头添加: import os os.environ['TCL_LIBRARY'] = '/path/to/tcl8.6' os.environ['TK_LIBRARY'] = '/path/to/tk8.6'关键参数:
--add-binary将 tcl/tk 目录作为二进制资源嵌入;--add-data确保 PyMySQL 模块被包含;--icon指定应用图标(.ico格式,非.png)。
6.2 PyQt5 版打包:用--exclude-module排除无用 Qt 模块,减小体积 40%
PyQt5 打包后默认 150MB+,实际图书馆系统只用QtWidgets和QtSql,可排除QtWebEngine、QtBluetooth等:
pyinstaller ^ --onefile ^ --exclude-module PyQt5.QtWebEngine ^ --exclude-module PyQt5.QtWebEngineCore ^ --exclude-module PyQt5.QtWebChannel ^ --exclude-module PyQt5.QtBluetooth ^ --exclude-module PyQt5.QtPositioning ^ --add-data "C:\Python310\Lib\site-packages\PyQt5\Qt\plugins;./plugins" ^ --icon=app.ico ^ main.py注意:
--add-data必须包含plugins目录,否则启动报Could not find the Qt platform plugin "windows";--exclude-module可大幅缩减体积,但需确认源码未引用被排除模块。
6.3 无 MySQL 服务的终极方案:SQLite 替代方案与数据迁移脚本
客户环境无法装 MySQL?用 SQLite 替代,但需处理三类差异:
| MySQL 特性 | SQLite 等效方案 | 迁移要点 |
|---|---|---|
AUTO_INCREMENT | INTEGER PRIMARY KEY | SQLite 的PRIMARY KEY自增,无需声明AUTO_INCREMENT |
DATETIME DEFAULT CURRENT_TIMESTAMP | TEXT DEFAULT (datetime('now')) | SQLite 用datetime('now'),且字段类型为TEXT |
FOREIGN KEY | FOREIGN KEY (...) REFERENCES ... | SQLite 3.6.19+ 支持,但需PRAGMA foreign_keys = ON |
提供一键迁移脚本mysql_to_sqlite.py:
import sqlite3 import pymysql def migrate_to_sqlite(mysql_config, sqlite_path): # 连接 MySQL mysql_conn = pymysql.connect(**mysql_config) cursor = mysql_conn.cursor() # 创建 SQLite 连接 sqlite_conn = sqlite3.connect(sqlite_path) sqlite_cursor = sqlite_conn.cursor() # 启用外键 sqlite_cursor.execute("PRAGMA foreign_keys = ON") # 迁移 books 表(转换字段类型) sqlite_cursor.execute(""" CREATE TABLE books ( isbn TEXT PRIMARY KEY, title TEXT NOT NULL, author TEXT DEFAULT '', publisher TEXT DEFAULT '', publish_year INTEGER DEFAULT NULL, category_id INTEGER DEFAULT NULL, stock INTEGER DEFAULT 1, created_at TEXT DEFAULT (datetime('now')) ) """) # 导出 MySQL 数据并插入 SQLite cursor.execute("SELECT * FROM books") rows = cursor.fetchall() sqlite_cursor.executemany( "INSERT INTO books VALUES (?, ?, ?, ?, ?, ?, ?, ?)", rows ) sqlite_conn.commit() mysql_conn.close() sqlite_conn.close() print("迁移完成!") # 使用示例 migrate_to_sqlite( mysql_config={'host': '127.0.0.1', 'user': 'root', 'password': 'pwd', 'database': 'library_db'}, sqlite_path='library.db' )最后一句:我坚持在所有交付项目里附带
library.db和library.sql两个文件——前者是开箱即用的 SQLite 数据库,后者是 MySQL 建表语句,客户想换数据库时,连读文档的时间都省了。希望帮到你。
本文还有配套的精品资源,点击获取