ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python连接MySQL实战:从PyMySQL到Flask Web应用

Python连接MySQL实战:从PyMySQL到Flask Web应用 1. 环境准备先把 Python 和 MySQL 伺候好1.1 安装与验证别在这一步翻车先说环境。很多人一上来就急着写代码结果连 Python 和 MySQL 都没装明白后面全是连锁问题。我这里按最常见的 Windows 环境讲Linux 和 macOS 的思路差不多命令上稍微改改。Python 推荐直接从官网下载安装包安装时记得勾选Add Python to PATH这一步不勾后面在命令行敲python会提示不是内部或外部命令到时候还得手动配环境变量纯属自找麻烦。装完在终端跑一下python --version pip --version两个命令都有正常输出说明 Python 和包管理工具就绪了。建议用 Python 3.8 以上版本PyMySQL 对 3.8 以上的支持很稳Python 3.7 以下某些语法特性会受限没必要给自己挖坑。MySQL 这边安装时选Server only就够了开发机没必要装全家桶。安装过程中会设置 root 密码记住这个密码后面所有连接都会用到。Windows 上装完 MySQL 8.0 之后服务默认是自动启动的可以在服务管理里确认一下。Linux 上则是sudo systemctl status mysql看到active (running)就说明服务正常。然后验证一下能否登录mysql -u root -p能进到mysql提示符环境就算打通了。1.2 驱动选型PyMySQL 为什么是默认选择Python 连 MySQL 的方式主流有三个驱动本质优点缺点PyMySQL纯 Python 实现安装简单跨平台兼容性好性能比 C 扩展略低mysql-connector-python官方驱动官方维护支持新特性安装包略重某些版本 API 有变动SQLAlchemyORM 框架屏蔽 SQL 细节切换数据库方便学习曲线陡复杂查询反而不直观我个人推荐从PyMySQL入门。原因有三一是纯 Python 实现源码就摆在那出了问题能直接去看它内部逻辑二是 pip 安装一条命令的事三是绝大多数 Python Web 框架Flask、Django 等都能无缝对接。SQLAlchemy 这类 ORM 等你把原生 SQL 玩熟了再上也不迟否则你根本不知道它在背后替你做了什么。安装命令pip install pymysql装完打开 Python 交互式环境输入import pymysql不报错就成功了。2. 连接数据库从连接参数到连接池2.1 连接参数拆解每个字段都有讲究接下来是核心环节。PyMySQL 连接 MySQL 的最小代码如下import pymysql conn pymysql.connect( hostlocalhost, port3306, userroot, passwordyour_password, databaseyour_db, charsetutf8mb4 )这六个参数我会在实战里一个个拆开讲再补充几个容易踩坑的细节。host 和 portlocalhost表示本机连接走的是 TCP 协议。如果 MySQL 部署在远程服务器上这里填服务器的 IP 或域名。port默认 3306除非安装时手动改过端口否则不要乱动。user 和 password默认用 root 账号没问题但生产环境强烈建议创建独立账号只授予业务需要的权限别让应用拿 root 全权限跑。database连接时指定默认数据库后续执行 SQL 就不用频繁写库名.表名。如果暂时不确定数据库名这个参数可以留空在代码里再USE。charset我习惯固定用utf8mb4这是 MySQL 8.0 推荐的字符集能完整支持四字节的 emoji 表情字符。如果你用utf8存储生僻字或 emoji 时会出现Incorrect string value的报错排查起来挺费时间的。2.2 游标与连接管理写代码的正确姿势拿到连接对象后要操作数据库还得创建游标cursor conn.cursor()游标可以理解为数据库返回结果的“指针”它负责执行 SQL 并持有查询结果集。默认游标返回的是元组类型每行数据是一条元组取字段时得靠下标比如row[0]。这个对代码可读性不太友好所以我在实战里一般用字典游标cursor conn.cursor(pymysql.cursors.DictCursor)这样每一行就是字典row[name]直接按字段名取值代码一眼看懂。连接用完必须关闭不然会占用数据库资源。完整写法是用with语句with pymysql.connect( hostlocalhost, userroot, passwordyour_password, databaseyour_db, charsetutf8mb4 ) as conn: with conn.cursor(pymysql.cursors.DictCursor) as cursor: cursor.execute(SELECT * FROM users) rows cursor.fetchall() for row in rows: print(row)with块退出时自动关闭游标和连接不需要手动调用close()代码简洁也不会泄漏连接。注意一点with conn只负责关闭连接不会自动提交事务增删改操作后还是要显式调用conn.commit()。2.3 连接池设计为什么每次请求都要复用连接有个高频问题每次操作数据库都新建连接用完就丢这样行不行小项目没问题但并发一上来就会暴露问题。MySQL 的每一次连接都要经历 TCP 握手、身份认证、权限校验这些流程很消耗时间和资源。假设一个接口同时有 100 个请求进来每个请求都新建连接服务端会直接被拖垮。所以在实战项目里我会引入DBUtils连接池pip install dbutils用法很简单from dbutils.pooled_db import PooledDB import pymysql pool PooledDB( creatorpymysql, maxconnections10, mincached2, maxcached5, blockingTrue, hostlocalhost, userroot, passwordyour_password, databaseyour_db, charsetutf8mb4 ) conn pool.connection() cursor conn.cursor()连接池启动时预创建mincached2个连接请求来了优先从池子里取用完了归还不够了再新建上限 10 个。blockingTrue表示池满时请求排队等待而不是直接报错。这个对 Web 应用来说是必须的数据库连接完全不建议裸连。3. 增删改查实战游标操作与 SQL 细节3.1 建表与插入注意 commit 这关键一步先建一张用户表CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, age INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );然后写插入逻辑import pymysql conn pymysql.connect( hostlocalhost, userroot, passwordyour_password, databaseyour_db, charsetutf8mb4 ) cursor conn.cursor() sql INSERT INTO users (name, email, age) VALUES (%s, %s, %s) data (张三, zhangsanexample.com, 25) cursor.execute(sql, data) conn.commit() print(f插入成功ID {cursor.lastrowid}) cursor.close() conn.close()这里有两个重点。第一个是占位符%s。PyMySQL 用%s作为参数占位符不管字段是字符串、数字还是日期统一用它。执行时把真实数据以元组形式传给execute的第二个参数驱动会自动完成转义和类型转换。千万不要用字符串拼接 SQL这会引发 SQL 注入漏洞后面我会详细说。第二个是commit()。MySQL 默认开启事务PyMySQL 连接到关闭连接之间的增删改操作属于事务范畴必须提交才会真正写入表。commit()在哪一步调用很讲究我习惯先把多条相关的增改操作全部执行完确认无误后统一commit()这样能保证数据一致性。如果中间任何一步出错调用conn.rollback()回滚可以撤销所有未提交的更改。少量测试数据要插入多条可以用executemanysql INSERT INTO users (name, email, age) VALUES (%s, %s, %s) data_list [ (李四, lisiexample.com, 30), (王五, wangwuexample.com, 28), (赵六, zhaoliuexample.com, 22) ] cursor.executemany(sql, data_list) conn.commit() print(f批量插入 {cursor.rowcount} 条)rowcount返回受影响的行数可以用来验证操作是否成功。3.2 查询与遍历fetch 家族三兄弟查询数据最常用的方法有三个fetchone()、fetchall()、fetchmany()。sql SELECT id, name, email, age FROM users WHERE age %s cursor.execute(sql, (22,)) # 方式一取一条 row cursor.fetchone() # 方式二取所有 rows cursor.fetchall() # 方式三分批取 rows cursor.fetchmany(size2)fetchone()适合只需要结果集第一行的场景如果查询结果为空返回None。fetchall()一次性把所有结果加载到内存数据量小没问题几万条以上就有爆内存的风险。fetchmany(size100)分批取适合大结果集遍历每次捞 100 条处理完再捞下一批。实际项目中我习惯这样写sql SELECT id, name, email, age FROM users cursor.execute(sql) while True: batch cursor.fetchmany(size100) if not batch: break for row in batch: print(row)这样不管表里有多少条数据内存占用始终可控。3.3 参数化查询为什么不让你拼 SQL这是新手最容易犯的严重错误。有些人图省事直接这样写# 危险写法千万别学 name input(请输入用户名) sql fSELECT * FROM users WHERE name {name} cursor.execute(sql)如果用户输入的是 OR 11拼出来的 SQL 就变成SELECT * FROM users WHERE name OR 11这个条件恒为真整张表的数据全被查询出来。如果是 DELETE 语句整个表都可能被清空。这就是典型的 SQL 注入攻击。正确的做法是参数化sql SELECT * FROM users WHERE name %s cursor.execute(sql, (name,))PyMySQL 会把传入的参数做严格的类型检查和转义处理从根源上杜绝注入问题。所有接受外部输入的 SQL 操作必须用参数化查询这是原则没有任何例外。不要相信任何前端传过来的数据哪怕你已经在前端做了校验后端也必须再拦一道。4. 前端 HTML 与 MySQL 的间接交互打通 Web 链路4.1 用 Flask 搭一座桥把数据库数据渲染到页面数据库读写搞明白了接下来看标题里的 HTML 怎么参与进来。严格来说浏览器里的 HTML 页面不能直连 MySQL——浏览器是前端MySQL 是数据库服务端两者之间没有直接的通信协议也不能把数据库账号密码直接写在网页里那是巨大的安全隐患。真正的工作模式是浏览器发 HTTP 请求 → 后端程序处理 → 后端连 MySQL 取数据 → 返回渲染好的 HTML 页面。后端就是那座桥。我这边实战例子用 Flask 来搭。先装依赖pip install flask pymysql写一个最简单的 Web 应用from flask import Flask, render_template import pymysql app Flask(__name__) def get_db(): return pymysql.connect( hostlocalhost, userroot, passwordyour_password, databaseyour_db, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) app.route(/) def index(): conn get_db() cursor conn.cursor() cursor.execute(SELECT id, name, email FROM users ORDER BY id DESC) users cursor.fetchall() cursor.close() conn.close() return render_template(index.html, usersusers) if __name__ __main__: app.run(debugTrue)模板index.html放在templates目录下!DOCTYPE html html langzh-cn head meta charsetutf-8 title用户列表/title /head body h1用户列表/h1 table border1 tr thID/th th姓名/th th邮箱/th /tr {% for user in users %} tr td{{ user.id }}/td td{{ user.name }}/td td{{ user.email }}/td /tr {% endfor %} /table /body /html跑起来之后浏览器访问http://127.0.0.1:5000数据库里的用户记录就显示在 HTML 表格里了。这里的逻辑是后端把查询结果通过 Flask 的模板引擎变量users传给了模板模板用{% for %}循环渲染出每一行。这就是前后端交互的基础模型。4.2 表单提交与数据落库完整闭环光展示数据还不够用户要在页面上提交数据写入数据库这就要处理 POST 请求了。改一下后端from flask import Flask, render_template, request, redirect, url_for app.route(/add, methods[GET, POST]) def add_user(): if request.method POST: name request.form[name] email request.form[email] age request.form[age] conn get_db() cursor conn.cursor() sql INSERT INTO users (name, email, age) VALUES (%s, %s, %s) cursor.execute(sql, (name, email, int(age))) conn.commit() cursor.close() conn.close() return redirect(url_for(index)) return render_template(add.html)add.html模板里放一个表单!DOCTYPE html html langzh-cn head meta charsetutf-8 title添加用户/title /head body h1添加用户/h1 form methodPOST action/add p姓名input typetext namename/p p邮箱input typeemail nameemail/p p年龄input typenumber nameage/p button typesubmit提交/button /form /body /html这段代码演示了完整的链路用户在浏览器填写表单浏览器把表单数据打包成 HTTP POST 请求发送到/addFlask 从request.form里取出字段值后端用参数化 SQL 写入 MySQL写完后重定向回首页前端重新查询并展示最新数据另外浮现在这个环节的数据库连接我在这里是每次请求新建连接这在开发时没问题但生产环境一定要替换成前面讲过的连接池否则并发一上来服务就会卡死。4.3 AJAX 无刷新交互用户体验更丝滑传统的表单提交会整页刷新现代网页交互通常用 AJAX 来实现局部更新。比如用户点击按钮后前端用 JavaScript 发一个异步请求给后端后端处理完返回 JSON 数据前端再局部渲染 DOM。这种方式不用跳页面交互更顺滑。前端部分!DOCTYPE html html langzh-cn head meta charsetutf-8 titleAJAX 添加用户/title /head body h1AJAX 添加用户/h1 input typetext idname placeholder姓名 input typeemail idemail placeholder邮箱 input typenumber idage placeholder年龄 button onclickaddUser()提交/button div idresult/div script function addUser() { const data { name: document.getElementById(name).value, email: document.getElementById(email).value, age: document.getElementById(age).value }; fetch(/api/add, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify(data) }) .then(res res.json()) .then(data { document.getElementById(result).textContent data.message; }); } /script /body /html后端部分from flask import Flask, request, jsonify import pymysql app.route(/api/add, methods[POST]) def api_add(): data request.get_json() conn get_db() cursor conn.cursor() try: sql INSERT INTO users (name, email, age) VALUES (%s, %s, %s) cursor.execute(sql, (data[name], data[email], int(data[age]))) conn.commit() return jsonify({success: True, message: 添加成功}) except Exception as e: conn.rollback() return jsonify({success: False, message: str(e)}) finally: cursor.close() conn.close()AJAX 模式的好处是不打断用户操作同时前端拿到返回结果后可以做各种自定义的反馈比如弹提示、刷新局部列表等。生产级项目基本都走这条路线。5. 常见问题与排查技巧实录5.1 高频报错速查表直接对号入座我把实际开发中遇到的频率最高的几个问题整理了一下每一步的排查思路也附在上面你照着一步步来基本能解决报错信息原因解决方案Access denied for user rootlocalhost密码错误或账号无权限检查连接密码或确认账号是否允许对应主机访问Host xxx is not allowed to connectMySQL 只允许本机连接拒绝远程 IP在 MySQL 里给账号授权远程访问GRANT ALL ON *.* TO user%然后FLUSH PRIVILEGESUnknown database xxxdatabase 参数指定的库不存在先连接 MySQL 执行CREATE DATABASE xxx确认库名拼写Table xxx.users doesnt exist表名或库名错误检查表名与库名是否匹配注意大小写Incorrect string value字符集不支持特殊字符统一使用utf8mb4包括连接参数和表结构调整Lost connection to MySQL server during query超时或数据量过大增大 MySQL 的wait_timeout或优化 SQL 语句加索引Cant connect to MySQL server on localhostMySQL 服务没启动或端口不对检查服务状态确认端口号是否为 3306MySQL Connection not available连接池里的连接被 MySQL 服务端断开给 PyMySQL 连接加autocommitTrue或者设置连接池的连接检测机制5.2 三个容易忽略的坑踩一次就长记性坑一字符集不统一。数据库表、连接参数、HTML 页面编码各管各的结果前台显示中文全是问号。我的习惯是从建库开始就统一字符集CREATE DATABASE your_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;连接参数的charsetutf8mb4必须写上HTML 的meta charsetutf-8也别漏。三层全统一基本不会出现乱码。坑二事务忘记提交。插入了数据程序也没报错但查数据库就是没有。这是新手最经典的低级错误。解决办法是形成条件反射——任何INSERT、UPDATE、DELETE操作之后立刻写conn.commit()如果要调试就加打印日志确认提交成功。坑三忘记关闭连接。短时间没问题跑上一段时间突然报Too many connections。这就是连接泄漏了。排查方法是执行 MySQL 命令SHOW PROCESSLIST看是否有大量Sleep状态的连接堆积。解决办法是用with上下文管理器或者连接池方案连接不用了及时归还。5.3 性能调优入门从索引和自动化查两个方向突破当数据量上到百万级别全表扫描会让查询慢到无法接受。MySQL 的性能优化第一张牌永远是索引。CREATE INDEX idx_email ON users(email); CREATE INDEX idx_age ON users(age);有了索引WHERE email xxx这种条件查询就能走索引避免全表扫描。但要知道索引不是越多越好它会增加写入开销每次插入都要同步维护索引树。一个表建 3 到 5 个高频查询索引是合理的别把每个字段都加一遍。打开慢查询日志能帮你精准定位哪些 SQL 拖慢了项目SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;超过 1 秒的查询会被自动记录下来。重点分析这些慢 SQL配合EXPLAIN查看执行计划EXPLAIN SELECT * FROM users WHERE email zhangsanexample.com;看type字段是不是ALL全表扫描如果是说明索引没建对或没生效。用好了这个组合拳多数性能问题都能被快速定位。6. 从学习到实战我的几个经验建议再多说几句我的实际体会。第一别过度设计架构。很多人学完这些问题后动不动就想着引 ORM、上 Redis 缓存、搞微服务。但一个只有几百用户在线的项目用原生 SQL 加连接池完全够用。架构是长出来的不是一开始就设计出来的过度设计反而让维护成本翻倍。第二调试时把 SQL 打出来。PyMySQL 不会自动打印执行的 SQL出错时往往只看到堆栈。我的做法是封装一层数据库操作类统一记录cursor._last_executed这个属性保存了真正发到 MySQL 执行的 SQL 语句。出错时打印它问题基本就定位了一半。第三把数据库密码放在配置里别写在代码里。项目越来越大后代码会被到处复制、分享、提交到仓库。密码写在代码里就像把家门钥匙贴在门上。Flask 项目可以用环境变量或者.env文件管理配置DB_HOSTlocalhost DB_USERroot DB_PASSWORDyour_password DB_NAMEyour_db代码里通过os.getenv(DB_PASSWORD)读取这样即使代码泄露数据库还有一层保护。最后再补一个项目结构建议。学 Python 和 MySQL 的交互不要只停留在跑通 demo试着做一个完整的后台管理系统带登录认证、数据列表、增删改查、搜索分页这些功能。做完这个项目你对数据库交互的理解会上一个台阶后面再做任何涉及存储的功能心里都很有底。
返回列表