数据库课设实战:Python pymysql 构建员工信息管理系统 简介这是一份面向数据库课程设计的员工信息管理系统完整源码包适合正在完成课设的高校学生及需要练习 Python 与 MySQL 联调的开发者。资源基于 Python 和 pymysql 实现后端业务逻辑配合 MySQL 完成员工、部门等表结构的增删改查覆盖数据库设计、SQL 操作、事务与错误处理等关键知识点。压缩包共 9 个文件含 2 个 Python 源文件、4 个架构设计图含 E-R 图与数据流图、2 份课程设计报告及 1 份 README 说明整体大小约 6.71MB结构紧凑便于按图索骥。配套报告中梳理了从建库建表到连接查询的完整流程设计图则可帮助理解表关系与数据流向方便直接复用或在此基础上扩展界面与权限功能。目前已有 4343 人学习下载对需要快速搭建课设框架并理解数据库实操的读者很有参考价值。1. 数据库课设选型为什么用 Python MySQL 做员工信息管理系统数据库课程设计最尴尬的场面不是答辩时被问到不会的SQL而是被问到“你的表设计为什么这么分”时答不上来。“员工信息管理系统”这个题目每年都有大量学生在做但大多数翻车点都不在界面而在数据库设计规范、连接安全和统计查询的合理性上。这套方案选择 Python MySQL 实现核心原因是 Python 的 pymysql 封装足够简单能让你把精力集中在表结构设计和 SQL 调优上而不是像 Java 那样花大量时间配置 JDBC。方案面向正在准备数据库课设或毕设的学生也适合需要快速交付内部小工具的开发人员。接下来从需求分析、数据库设计、核心实现、避坑、测试部署到答辩验收给出一条可复制的完整路径。2. 需求分析与总体设计功能模块、五张表与三层结构2.1 业务需求拆解从“能跑”到“能答辩”的功能清单很多同学拿到题目就开始写代码结果做到一半发现表结构设计不合理又推倒重来。我在做这个项目时第一件事不是建表而是先把用户角色和业务动作理清楚。员工信息管理系统在数据库课程设计里至少要覆盖四个核心业务域员工基本信息管理入职、转岗、离职、部门维护、薪资记录、用户登录与操作追溯。角色上我一般会分两级管理员拥有全部读写权限普通用户只能查询。这样就有了权限校验的落点也能在答辩时讲清楚“为什么需要一个单独的登录表”。以我负责的模拟项目X为例具体功能清单如下。部门管理负责对部门信息进行增删改查部门表被员工表引用删除前需检查是否有关联员工员工管理负责员工信息的完整 CRUD包括姓名、性别、出生日期、入职日期、所属部门、岗位、联系方式、状态薪资管理按月记录员工薪资支持按部门汇总和按时间区间查询登录与权限基于用户表校验密码区分管理员与普通用户操作日志记录每次增删改操作便于审计。这个清单看起来普通但它是答辩时的“功能覆盖度”论证基础。注意员工表和部门表之间一定要演示外键关系但外键约束到底加不加取决于你讲不讲得清。我的建议是加上因为课程设计考察的恰恰是你能不能理解外键带来的数据一致性。答辩时老师通常会问“为什么员工表里要有 dept_id 而不是直接把部门名称写进去”答案就是规范化设计避免冗余更新异常。把这个逻辑讲清楚比多写一百行代码都管用。2.2 数据库设计五张表如何支撑员工全生命周期数据库设计是整份方案的核心也是评分权重最高的地方。我推荐使用 5 张表来支撑整个系统用户表 user、部门表 department、员工表 employee、薪资表 salary、操作日志表 operation_log。下面给出 MySQL 建表语句注意字符集统一使用 utf8mb4避免中文乱码。-- 用户表用于登录和权限控制 CREATE TABLE user ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, role ENUM(admin,user) NOT NULL DEFAULT user, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 部门表 CREATE TABLE department ( dept_id INT AUTO_INCREMENT PRIMARY KEY, dept_name VARCHAR(100) NOT NULL UNIQUE, manager VARCHAR(50) DEFAULT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 员工表 CREATE TABLE employee ( emp_id INT AUTO_INCREMENT PRIMARY KEY, emp_name VARCHAR(50) NOT NULL, gender ENUM(M,F) NOT NULL, birth_date DATE NOT NULL, hire_date DATE NOT NULL, dept_id INT NOT NULL, position VARCHAR(50) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, status ENUM(active,inactive) DEFAULT active, CONSTRAINT fk_employee_dept FOREIGN KEY (dept_id) REFERENCES department (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 薪资表 CREATE TABLE salary ( salary_id INT AUTO_INCREMENT PRIMARY KEY, emp_id INT NOT NULL, month CHAR(7) NOT NULL, amount DECIMAL(10,2) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_salary_emp FOREIGN KEY (emp_id) REFERENCES employee (emp_id) ON DELETE CASCADE, UNIQUE KEY uk_emp_month (emp_id,month) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 操作日志表 CREATE TABLE operation_log ( log_id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, action VARCHAR(50) NOT NULL, target_table VARCHAR(50) NOT NULL, target_id INT DEFAULT NULL, detail VARCHAR(500) DEFAULT NULL, log_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_log_user FOREIGN KEY (user_id) REFERENCES user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这份建表设计有四个关键决策值得在答辩时主动讲。第一用户表单独建而不是挂在员工表上因为登录凭证和员工信息生命周期不同第二员工表和部门表用外键约束确保不会出现“孤儿员工”第三薪资表加了(emp_id, month)唯一键避免同一员工同一个月被插入两次第四操作日志表只记关键动作不做业务查询所以没有冗余字段。注意password_hash字段长度给到 255是为了后面能存哈希结果而不是明文密码。如果课设只要求简单实现也可以存明文但答辩时会被追问安全性所以建议至少用sha256处理一下。2.3 总体架构三层结构与代码组织系统采用经典的三层结构表示层 UI、业务层 Service、数据访问层 DB。表示层可以是命令行界面或 Tkinter 窗口业务层写 CRUD 和统计逻辑数据访问层封装所有 SQL 操作。这样分层的好处是即使答辩时老师让你现场加一个功能你也不用重写界面或 SQL。我一般会按下表组织代码目录employee_system/ │ ├── db.py # 数据库连接与连接池 ├── models.py # 数据实体类只传参不碰SQL ├── services.py # 业务逻辑员工、部门、薪资操作 ├── ui.py # 交互界面命令行或Tkinter ├── main.py # 程序入口 ├── requirements.txt # 依赖清单 └── sql/ ├── init.sql # 建库建表脚本 └── seed.sql # 演示数据每层之间的调用关系是main.py 启动 ui.pyui.py 调用 services.py 的方法services.py 内部通过 db.py 拿到连接并执行 SQL。models.py 只用来传参不直接操作数据库。这种结构看上去比把所有代码写在一个文件里多花十几分钟但在课设答辩时价值很大。老师很可能会问“你把自己的功能模块画一下”这时候你直接拿这个目录结构就能讲清楚数据流。这里有一个容易被忽略的边界业务层不能直接暴露 SQL 语句给界面层否则后面替换数据库时所有 SQL 都要改。我通常会在 db.py 里放一个通用执行方法业务层只能传入 SQL 和参数。这样以后从 MySQL 换成 PostgreSQL代价很小。为了进一步约束可以在 services.py 里先定义接口再写实现比如EmployeeService类里有add_employee、update_employee、search_employee等方法ui.py 只依赖这几个方法名不关心底层是什么数据库。3. 核心实现pymysql 连接、CRUD 与统计查询的落地3.1 数据库连接封装pymysql 连接与连接池Python 操作 MySQL 最常用的库是 pymysql它是纯 Python 实现的驱动免编译在课设环境里三分钟就能跑起来。很多人习惯在每个函数里直接 connect写完代码发现连接数爆掉。正确的做法是封装一个数据库连接对象并做最小可用连接池。下面是我在模拟项目 X 中用的连接封装。# db.py import pymysql from dbutils.pooled_db import PooledDB POOL PooledDB( creatorpymysql, maxconnections8, mincached2, maxcached5, blockingTrue, hostlocalhost, port3306, userroot, passwordyour_password, databaseemployee_db, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) def get_conn(): 获取连接 return POOL.connection() def query(sql, argsNone): conn get_conn() try: with conn.cursor() as cur: cur.execute(sql, args) return cur.fetchall() finally: conn.close() def execute(sql, argsNone): conn get_conn() try: with conn.cursor() as cur: cur.execute(sql, args) conn.commit() return cur.rowcount except Exception: conn.rollback() raise finally: conn.close()这里有几个参数值得记maxconnections8是连接池最大连接数blockingTrue表示连接池满时等待而不是报错cursorclass用DictCursor这样查出来直接是字典列表用起来比元组顺手。query负责查询并返回结果execute负责增删改并自动提交。关键点是execute里commit和rollback的位置。如果你忘了rollback一个 SQL 执行失败会让后续操作都处于脏状态这就是常见的“为什么我明明有异常但是数据还是写进去了一半”。安装依赖只需要pip install pymysql dbutils然后把requirements.txt里写上这两个包。如果是在学校机房没有外网装包可以先把两个 whl 文件放到 U 盘里离线安装也不会丢人。3.2 员工 CRUD 实现参数化查询防注入员工管理模块的核心是新增、修改、删除、查询四个操作。如果直接做字符串拼接 SQL系统几乎一定存在 SQL 注入漏洞。虽然课设环境是本地但导师通常会在答辩时问“你的查询能防注入吗”。下面给出新增和修改的实现。# services.py from db import execute, query def add_employee(emp): sql INSERT INTO employee (emp_name, gender, birth_date, hire_date, dept_id, position, phone, status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) args (emp[name], emp[gender], emp[birth_date], emp[hire_date], emp[dept_id], emp.get(position), emp.get(phone), emp.get(status, active)) return execute(sql, args) def update_employee(emp_id, fields): # 动态拼接字段名但值仍然走参数化 allowed_keys {emp_name, gender, birth_date, hire_date, dept_id, position, phone, status} set_clause , .join(f{key} %s for key in fields if key in allowed_keys) if not set_clause: return 0 args [fields[key] for key in fields if key in allowed_keys] [emp_id] sql fUPDATE employee SET {set_clause} WHERE emp_id %s return execute(sql, args)注意update_employee的动态拼接虽然用了 Python 格式字符串但拼的是字段名而不是值值仍然由%s占位符传到 MySQL。字段名做了白名单校验所以不存在注入风险。这也是一个可以拿到答辩会上讲的细节“为什么字段名可以用拼接而值一定要参数化”。删除操作要关联判断员工是否已有薪资记录如果有就不能物理删除只能把status改成inactive否则会破坏薪资表的外键关系。查询操作可以做一个模糊搜索。def search_employee(keyword): sql SELECT e.*, d.dept_name FROM employee e LEFT JOIN department d ON e.dept_id d.dept_id WHERE e.emp_name LIKE %s OR d.dept_name LIKE %s like f%{keyword}% return query(sql, (like, like))LIKE 配合参数化时模糊匹配符号要放在传入参数里而不是在 SQL 里写字符串拼接。这个细节很容易被忽视但一旦写成参数化SQL 注入基本就堵死了。3.3 复杂查询与统计部门人数、薪资分布、入职时间课设想拿高分只靠单表 CRUD 不够至少要有两个 GROUP BY 统计。这里准备三个常用统计查询按部门统计员工数、按月份汇总薪资、按入职年份统计新增人数。这些查询能体现分组聚合函数的使用能力。-- 按部门统计员工数 SELECT d.dept_name, COUNT(e.emp_id) AS emp_count FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id GROUP BY d.dept_id, d.dept_name HAVING COUNT(e.emp_id) 0 ORDER BY emp_count DESC; -- 按月份汇总薪资 SELECT month, SUM(amount) AS total_salary FROM salary GROUP BY month ORDER BY month DESC; -- 按入职年份统计新增人数 SELECT YEAR(hire_date) AS hire_year, COUNT(*) AS total FROM employee GROUP BY YEAR(hire_date) ORDER BY hire_year;第一个查询用了 LEFT JOIN因为部门表里可能有刚创建还没有员工的部门。如果用 INNER JOIN那些空部门会被过滤掉答辩时可能被追问。HAVING 是在分组后过滤WHERE 是在分组前过滤这个区别是高频考点。第二个查询的month字段是CHAR(7)格式如2025-03所以按字符串排序不会乱。第三个查询用YEAR()函数提取年份在 Python 端直接调用query()执行返回结果就是列表可以直接打印成表格或写进报表。3.4 界面与交互命令行还是 Tkinter很多同学纠结要不要做图形界面。我的看法是除非题目明确要求 GUI否则优先做命令行菜单。命令行界面干净、好展示还能让老师直接看 SQL 执行日志图形界面如果没做好反而暴露缺乏前端设计能力。这里给出命令行菜单的骨架。# ui.py def main_menu(): print(员工信息管理系统) print(1. 部门管理) print(2. 员工管理) print(3. 薪资查询) print(4. 统计报表) print(0. 退出) def run(): while True: main_menu() choice input(请选择) if choice 1: pass # 调用部门管理 elif choice 2: pass # 调用员工管理 elif choice 0: break if __name__ __main__: run()如果你时间充裕想用 Tkinter 做界面也可以但记得把界面逻辑和业务逻辑分开。否则每个按钮里都塞 SQL后期维护非常痛苦。从课设评分角度命令行已经足够展示对数据库设计的理解因为老师更关心的是数据模型和 SQL而不是界面是否华丽。4. 避坑指南数据库课设中最容易翻车的五个问题4.1 中文乱码建表、连接、终端三处的字符集都要统一现象是插入中文姓名后查询出来变成???或者乱码。原因通常是三处字符集不一致建库时用了默认的latin1连接时没指定charsetutf8mb4或者 Windows 终端本身是 GBK。解决方法是建库时显式指定DEFAULT CHARSETutf8mb4连接参数里也写上charsetutf8mb4并在代码开头加sys.stdout.reconfigure(encodingutf-8)处理终端输出。这三处都统一后中文基本不会再出问题。这一点我在一开始就写进init.sql后面所有模块都受益。4.2 连接失败MySQL 8.0 的认证插件与 pymysql 不兼容现象是pymysql连接时报错提示Authentication plugin caching_sha2_password cannot be loaded。原因是从 MySQL 8.0 开始默认认证插件改成了caching_sha2_password而旧版 pymysql 不认。解决方法是升级 pymysql 到较新版本因为新版本已经支持该插件。如果升级后还不行就在创建 MySQL 用户时指定认证插件CREATE USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;。需要注意后一种方案属于临时绕坑答辩时如果老师问起就说为了兼容本地课程设计环境并顺势说出两种插件的区别。4.3 数据写入失败或丢了一半事务忘了提交现象是插入员工资料后程序无异常但打开 MySQL 客户端查询看不到数据。原因是在execute里执行了INSERT但没调用connection.commit()pymysql 默认开启事务且不自动提交。解决方法是所有写操作结束后统一 commit遇到异常就 rollback。我之前把commit和rollback都封装在execute函数里这样业务层只需要调用一个方法不会再漏。答辩时被问“怎么保证数据一致性”时就直接讲这个封装的动机。4.4 查询结果为空SQL 或参数传错位置现象是搜索员工名字明明数据库里有记录fetchall()却返回空列表。原因可能是模糊查询的%没有被正确传递比如在 SQL 里写了LIKE %%s%或者参数没按顺序传。解决方法是把%放进 Python 参数里像前面的like f%{keyword}%然后 SQL 只写LIKE %s。另外要注意DictCursor下字段名是字典 key取值时用row[emp_name]而不是row[0]否则也会报奇奇怪怪的错误。4.5 外键约束导致删除报错没有想清楚级联策略现象是删除一个部门时系统报外键约束错误说 employee 表里有记录关联。原因是没有考虑删除策略。解决方法是三选一先删除该部门下的员工再删部门修改员工status为 inactive 而不是物理删除或者把外键的ON DELETE设为SET NULL。我通常选用逻辑删除因为它最符合员工管理系统的真实业务员工离职不代表记录要消失而是进入 inactive 状态。这一点在答辩时也能展示你对业务和约束之间取舍的思考。5. 测试与部署从本机运行到答辩演示的完整流程5.1 测试用例设计增删改查的边界与异常写完代码后不能直接拿去答辩要先过一遍测试用例。我给模拟项目 X 设计了一套最小测试集覆盖正常流程和边界流程。正常流程包括新增一个空部门、新增员工、给员工录入薪资、按姓名查询、修改员工岗位、逻辑删除员工、重新激活员工边界流程包括重复部门名、重复(emp_id, month)薪资记录、删除有员工的部门、超长手机号、日期格式错误。每个用例都要记录输入、输出和数据库前后状态。举例来说测试新增员工时我不仅看返回值还会立刻查询员工表确认emp_id是否自增、dept_id是否和部门表对应。测试薪资重复插入时确认第二次插入被唯一键挡住并抛出异常。这套测试不需要写成自动化脚本但至少要在答辩前手跑一遍把结果截图或记录到日志里。老师问“你怎么测试”时能拿出这些用例比嘴上说“试过”强得多。5.2 运行环境准备Python、MySQL、依赖安装给出运行环境清单方便自己换机器也能跑。建议使用 Python 3.8 以上MySQL 8.0依赖包 pymysql 和 DBUtils。在根目录放一个requirements.txt内容就两行pymysql和dbutils。安装时执行pip install -r requirements.txt。如果机房没有外网就用 U 盘离线安装。数据库方面先执行sql/init.sql建库建表再执行sql/seed.sql插入演示数据。顺序不能反否则外键创建会失败。这里有一个我踩过的坑seed.sql里插入员工数据时如果部门表数据还没插入员工表会因为外键约束拒绝插入。所以演示数据的插入顺序必须是部门、用户、员工、薪资。实际项目中我干脆在init.sql末尾直接调用source seed.sql避免自己忘记顺序。这个习惯后来也带到了其他项目里。5.3 演示数据与演示脚本让答辩不冷场演示数据的设计很关键。不要只插三条记录否则统计查询没看头。我一般会用 Python 脚本批量生成数据模拟 5 个部门、50 名员工、12 个月的薪资记录然后通过execute批量写入。这样答辩时演示部门人数统计、薪资月度汇总一眼就能看出效果。生成脚本可以单独放在scripts/generate_data.py里方便重复生成。# scripts/generate_data.py import random from db import execute, query departments [研发部, 市场部, 人事部, 财务部, 行政部] for dept in departments: execute(INSERT INTO department (dept_name) VALUES (%s), (dept,)) for i in range(50): emp { name: f员工{i:03d}, gender: random.choice([M, F]), birth_date: f19{random.randint(70, 99)}-{random.randint(1,12):02d}-{random.randint(1,28):02d}, hire_date: f20{random.randint(10,24)}-{random.randint(1,12):02d}-{random.randint(1,28):02d}, dept_id: random.randint(1, len(departments)), position: 工程师, } add_employee(emp) # 复用 services 里的方法生成脚本的价值不只是造数还能检验你的插入接口是否稳定。如果 50 条数据里有几条失败说明代码里存在被忽略的脏数据问题。答辩时用这份数据跑完所有功能整个过程不需要临场输入数据自然不冷场。6. 验收与答辩让导师认可这份员工管理系统的价值6.1 验收清单功能完整性与数据一致性检查答辩前给自己列一份验收清单先当作甲方来验收。我会按五张表之间的关系逐项检查每个部门的员工数是否对得上每个员工的薪资条数是否等于实际录入月数逻辑删除的员工是否不在统计列表中操作日志是否记录了增删改的用户和动作。功能层面逐项跑一遍菜单确认每个选项都能正常退出没有死循环。最后把数据库备份一份万一答辩现场数据被搞乱还能用source backup.sql恢复。这个检查过程大约一小时但是最有效的安全感来源。6.2 答辩技巧从复述代码到讲设计决策答辩时不要逐行念代码老师不想听语法。通常是先讲需求再画表结构最后演示功能。讲表结构时重点说外键为什么这样设计、唯一键解决了什么问题。讲功能时带一句参数化查询防注入。最容易被追问的是统计查询所以要把 GROUP BY 和 HAVING 的区别提前背熟。回答不上来时不要硬编。可以说“这里我确实没有考虑到位我会用 DISTINCT 重查一次”然后用代码现场演示。课设的重点是数据库设计逻辑不是代码速度坦诚比硬撑更符合答辩场景。6.3 一个加分习惯先写验收清单再写代码从那以后我每次做数据库课设都会强制自己先写完验收清单再开始建表。这份清单让需求分析、表设计和代码实现都有了指向也让我在答辩时心里有底。如果你也需要这套完整源码、建库脚本和演示数据直接拿去对照实现即可。希望帮到你。本文还有配套的精品资源点击获取