简介:一份Python3连接并操作MySQL数据库的实战讲解资源,面向正在学习PyMySQL库的初中级Python开发者,也适合需要在多数据库环境中快速搭建连接模块的工程人员。内容基于Python3.7与PyMySQL0.9.3,围绕封装后的数据库连接类展开,覆盖连接配置、游标使用、SQL执行、事务提交与异常回滚等关键环节,并分别演示查询、增、删、改四类操作。资源为单个PDF文档,体积仅约50KB,可在手机或电脑上随时查阅。PDF内附完整类定义及do_one、select两个方法的逐段解析,包括连接参数dict的字段说明与返回结果结构,便于读者直接复制改造,迅速应用到实际项目中。该资源已有2395人学习浏览,适合想要通过一份精简文档快速掌握Python+MySQL核心操作的学习者。
1. Python3 连接 MySQL,先选对驱动再动手
Python3 连接 MySQL 做增删改查,是爬虫落库、后台接口、数据清洗脚本里绕不开的基础动作。很多人拿到需求就装个 PyMySQL 写 connect(),但连接参数、字符集、事务边界没想清楚,等数据写进去才发现乱码、丢更新或者连接被服务端掐断。这篇文章按「选驱动 → 建连接 → 写操作 → 管事务 → 排错」的顺序展开,从最小可运行代码讲到连接池和健康检查,适合刚接触 Python3 数据库编程的新手,也适合从其他语言转过来、想快速对齐细节的工程师。读完你应该能独立写出带参数化 SQL、事务和异常处理的存取代码。
2. Python3 与 MySQL 连接环境搭建:驱动选型、安装与最小连接
动手前先把驱动定下来。Python3 下连 MySQL 不像 JDBC 只有一条官方路径,社区和官方给了好几个选择,选错会在编译和认证阶段浪费不少时间。
2.1 PyMySQL 与 mysql-connector-python:两个常用驱动怎么选
| 驱动 | 安装方式 | 是否纯 Python | 典型场景 |
|---|---|---|---|
| MySQLdb / mysqlclient | pip install mysqlclient | 否,依赖 C 编译 | 遗留项目迁移、性能敏感 |
| PyMySQL | pip install pymysql | 是 | 绝大多数 Python3 新项目 |
| mysql-connector-python | pip install mysql-connector-python | 是 | 需要 Oracle 官方支持 |
MySQLdb 是 Python2 时代的主力,Python3 下对应 mysqlclient,但它依赖 C 编译,在 Windows 上装 wheels 偶尔要补运行库,为一个脚本折腾编译不值得。PyMySQL 是纯 Python 实现,pip 装上直接能用,支持 MySQL 5.7 到 8.0 的 caching_sha2_password 认证,社区资料最多,出问题一搜就有答案。mysql-connector-python 是官方出品,API 和 PyMySQL 高度相似,但包体积大,速度无明显优势,除非项目规定了官方依赖,我一般默认选 PyMySQL。
补一个兼容技巧:老项目代码里有 import MySQLdb 的,可以在入口处用 pymysql.install_as_MySQLdb() 做替换,避免改全量业务代码。注意这个方法只保证基础 API 兼容,别指望底层 C 类型也完全一致。
2.2 pip 安装并验证:Python3 环境常见坑
# 建虚拟环境,避免污染系统 Python(PEP 668 环境下 pip 会拒绝直接装) python3 -m venv .venv && source .venv/bin/activate pip install pymysql # 验证驱动可导入 python3 -c "import pymysql; print(pymysql.__version__)"前三行先建虚拟环境,这是系统 Python 普遍启用 externally-managed-environment 后最常见的坑,不建 venv 直接 pip install 会报错。后两行是验证,能打印版本号说明驱动装好了;如果 import 报错,先确认当前 shell 是否真的在虚拟环境里,再看 pip list。后面再用 pycharm 或 vscode 配好的 python 环境加载同一个虚拟环境,调试时可以直接在 IDE 里断点观察游标内容。
连接前还要确认 MySQL 服务本身在跑。装好 MySQL 8.0 后初始密码随安装方式不同:Windows 安装器会让你设置,Linux 仓库包则常写在 /var/log/mysql/error.log 里。我一般先用 mysql workbench 建一条连接验证账号能登录,再回过来排查 Python 侧,这样能把问题快速切成「MySQL 没起来」还是「Python 连不上」两段。
2.3 第一条 Python3 连接 MySQL 的代码:参数逐项说明
import pymysql conn = pymysql.connect( host='127.0.0.1', # MySQL 所在主机,跨机写内网 IP port=3306, # 默认端口,改过要同步 user='root', password='your_password', charset='utf8mb4', # 必须写,避免中文乱码 connect_timeout=3, # 秒,超时快速失败 ) with conn.cursor() as cur: cur.execute('SELECT VERSION()') print(cur.fetchone()) conn.close()connect() 返回的是连接对象,真正的 SQL 执行要交给游标 cursor。connect_timeout 很多人不写,默认值在 MySQL 不可达时会让程序卡很久,调试期建议显式设 3 秒。charset 用 utf8mb4 而不是 utf8,因为 MySQL 的 utf8 实际只存 3 字节,遇到 emoji 或生僻字会直接报错。host 写 127.0.0.1 而不是 localhost,两者在账号授权表里可能对应不同记录,本地开发没问题,跨机联调时经常遇到 1045 权限错误,属正常现象。
提示:with conn.cursor() 只关游标,不关连接。连接还是要手动 close(),或放进 finally 里保证释放。
3. Python3 操作 MySQL 增删改查:游标、参数化 SQL 与结果读取
连接建好后,真正的日常工作是增删改查。这一章从游标机制讲起,给出一套可以直接抄的 CRUD 代码,再讲清结果集怎么读才不容易踩内存和状态同步的坑。
3.1 游标与连接的关系:为什么 Python3 操作 MySQL 要先建 cursor
MySQL 连接本质是一条 TCP 长连接,把 SQL 发给服务端、再收回结果集。游标可以理解成挂在连接上的「结果集管道」:同一个连接可以反复创建多个游标,但同一时刻未读尽的结果集和下一次 execute 会互相干扰。默认的 Cursor 返回元组,每一行是 (id, name, age),适合代码里列顺序固定的场景;需要按列名取值时改成 pymysql.cursors.DictCursor,返回字典,可读性高很多。
一个常见误区是认为 with conn.cursor() 会把连接也关掉。实际上它只调 cursor.close(),把该游标未读完的结果清理掉,连接本身还活着。理解这点,写多批次操作时就不会因为「游标已关闭」而反复新建连接。
3.2 增删改查的最小代码:参数化 SQL 的四个例子
import pymysql conn = pymysql.connect( host='127.0.0.1', user='root', password='your_password', database='test_db', charset='utf8mb4', ) try: with conn.cursor() as cur: cur.execute( 'CREATE TABLE IF NOT EXISTS user_info (' ' id INT AUTO_INCREMENT PRIMARY KEY,' ' name VARCHAR(50) NOT NULL,' ' age INT NOT NULL DEFAULT 0,' ' created_at DATETIME DEFAULT CURRENT_TIMESTAMP)' ) # C:插入,%s 占位,值由第二个参数传入 cur.execute( 'INSERT INTO user_info (name, age) VALUES (%s, %s)', ('ali', 28), ) print('新插入 id:', cur.lastrowid) # U:更新,条件也走参数,绝不拼字符串 cur.execute( 'UPDATE user_info SET age = %s WHERE name = %s', (29, 'ali'), ) print('受影响行数:', cur.rowcount) # R:查询,带过滤和排序 cur.execute( 'SELECT id, name, age FROM user_info WHERE age > %s ORDER BY age DESC', (20,), ) for row in cur.fetchall(): print(row) # D:删除 cur.execute('DELETE FROM user_info WHERE name = %s', ('ali',)) conn.commit() finally: conn.close()四个操作共用同一个连接和同一个游标,commit 放在所有写操作之后,保证一组操作要么全部生效、要么全部回滚。lastrowid 是刚插入的自增主键,批量导入后要拿 id 关联外键时很有用;rowcount 表示 UPDATE/DELETE 实际影响的行数,不是「匹配到」的行数,返回 0 说明该行已不存在。
PyMySQL 的占位符是 %s,不管字段是字符串还是数字都写 %s,由驱动负责类型转换。严禁用 f-string 拼 SQL,比如f"SELECT * FROM t WHERE name='{name}'",这等于把 SQL 注入漏洞直接暴露给调用方;参数化之后特殊字符会被转义,注入和引号报错同时消失。调用存储过程也走同一套 execute,写成cur.execute('CALL proc(%s)', (arg,)),参数化规则不变。
3.3 fetchone、fetchmany、fetchall:查询结果怎么读
| 方法 | 返回 | 适用场景 |
|---|---|---|
| fetchone() | 单行或 None | 查单条记录、循环取数 |
| fetchmany(size) | 最多 size 行 | 分批处理大结果集 |
| fetchall() | 全部行 | 结果集小,一次载入内存 |
三个方法都在消费同一个结果集。fetchall 一次性把所有行读进内存,几千行没问题,几十万行会把 Python 进程内存顶爆。fetchmany(size) 适合边读边处理,配合生成器写成分批任务。还有一类流式读取场景,PyMySQL 提供 SSCursor,它不在客户端缓存结果,而是边读边从网络取,适合导出大表;副作用是读取期间该连接不能再执行其他 SQL,否则报 "Commands out of sync",这点很容易踩。
另外要提醒:同一个连接上,下一次 execute() 会丢掉上一次未读完的结果。如果你 fetchall 后没取完就想再查别的,先确认结果被消费干净,否则第二次查询拿到空结果或直接报错。习惯上一条结果集处理完再开下一条,两条查询交替用不同游标更安全。
4. 事务、连接池与异常处理:Python3 操作 MySQL 的进阶细节
基础 CRUD 跑通后,真正拉开差距的是事务边界、连接复用和异常定位。这三件事决定代码在并发和故障场景下是稳定还是频繁出暗病。
4.1 commit 与 rollback:Python3 操作 MySQL 的事务边界
PyMySQL 默认 autocommit=False,意味着每次 UPDATE/INSERT/DELETE 虽然执行成功,但只在当前事务里可见,必须 commit() 才算落盘。很多人写脚本没有 commit,程序退出后数据神秘消失,十有八九是这个原因。反过来,查询 SELECT 不需要 commit,它不改变数据。
try: with conn.cursor() as cur: cur.execute('UPDATE user_info SET age = age + 1 WHERE name = %s', ('ali',)) cur.execute('INSERT INTO user_log (name, action) VALUES (%s, %s)', ('ali', 'update_age')) conn.commit() except Exception: conn.rollback() raise把两个写操作放进同一个事务是正确的,第二个失败时 rollback 会把第一个也撤掉,避免「改了数据但没记日志」这类半截状态。注意 rollback 之后要重新 raise,否则调用方拿到的连接处于可用状态但事务上下文已经没了,继续往下走会产生幻觉数据。还有一点经常被忽略:MySQL 的 DDL(CREATE/ALTER/DROP)隐式提交,不能回滚,所以建表语句要放在事务外面。
4.2 用 DBUtils 连接池复用 Python3 的 MySQL 连接
短脚本里一次连接一次关闭没毛病,但 Web 接口每请求都建连,TCP 握手加认证的耗时会被放大。常见做法是用 DBUtils 的 PooledDB 维护一批连接,用完归还而不是关闭。注意新版 DBUtils 的导入路径已经变成 dbutils.pooled_db,老资料里的 from DBUtils.PooledDB 在新版本会直接 ImportError。
pip install DBUtilsfrom dbutils.pooled_db import PooledDB import pymysql pool = PooledDB( creator=pymysql, maxconnections=10, # 连接池最大容量 mincached=2, # 空闲时最少保留 maxcached=5, # 空闲时最多保留 blocking=True, # 池满时阻塞等待,而不是报错 ping=1, # 取连接时探活,防止拿到已被服务端断开的连接 host='127.0.0.1', user='root', password='your_password', database='test_db', charset='utf8mb4', ) conn = pool.connection() with conn.cursor() as cur: cur.execute('SELECT COUNT(*) FROM user_info') print(cur.fetchone()) conn.close() # 归还连接,不是真关闭maxconnections 是上限,超过后新请求按 blocking 决定是等待还是抛异常;ping=1 表示每次取连接时先做轻量探活,配合 MySQL 的 wait_timeout 默认 8 小时,能避免拿到早已被服务端回收的连接。pool.connection() 拿到的对象用法和普通连接一致,但 conn.close() 语义变成归还,归还后又调底层接口真的断开,连接池就白搭了。
4.3 异常类型与排错顺序
| 异常 | 典型错误码 | 含义 |
|---|---|---|
| pymysql.err.OperationalError | 2002 / 1045 / 2013 | 连不上、认证失败、连接中途断开 |
| pymysql.err.IntegrityError | 1062 / 1451 | 唯一键冲突、外键约束失败 |
| pymysql.err.ProgrammingError | 1064 / 1054 | SQL 语法错、列不存在 |
| pymysql.err.DataError | 1265 / 1366 | 数据超出字段范围或字符集不符 |
排错固定按四层走:第一,MySQL 本身活着吗,用 workbench 或 mysql 命令行试连;第二,账号授权对不对,跨机连接要确认 user 是 'user'@'%' 而不是只允许 localhost;第三,连接参数对不对,端口、密码、database 是否存在;第四,SQL 本身有没有问题,把报错里的 SQL 片段复制到命令行执行一遍。Python 侧所有数据库异常都挂在 Exception 下面,但不建议裸 except Exception 吞掉,至少把错误码打出来。
from pymysql.err import OperationalError, IntegrityError try: conn = pymysql.connect(host='127.0.0.1', user='root', password='x', database='test_db') except OperationalError as e: # e.args[0] 是错误码,e.args[1] 是可读信息 print(f'{e.args[0]}: {e.args[1]}')pymysql 的异常 args 第一个元素是 MySQL 错误码,第二个是消息文本,写日志时两个都要留。2002 表示 socket 连不上,先查服务端口;1045 是密码或授权问题,查账号权限;2013 Lost connection 多半是 SQL 太大或执行超过 net_read_timeout,要调服务端参数而不是改 Python。
5. 用最小健康检查脚本验证 Python3 连 MySQL 的每一层
这章给一个能直接落地的做法:把连接检查封装成自检函数,部署前和排障时各跑一次,把「服务没起」「授权不对」「SQL 写错」三层问题一次性曝出来。
import time import pymysql def check_mysql(host='127.0.0.1', user='root', password='', database='test_db', port=3306): start = time.time() try: conn = pymysql.connect( host=host, port=port, user=user, password=password, database=database, charset='utf8mb4', connect_timeout=3, ) conn.ping(reconnect=True) with conn.cursor() as cur: cur.execute('SELECT 1') assert cur.fetchone()[0] == 1 cur.execute("SHOW VARIABLES LIKE 'collation_server'") print('collation:', cur.fetchone()[1]) print(f'ok, {time.time() - start:.3f}s') conn.close() return True except Exception as e: print('failed:', type(e).__name__, e) return False check_mysql()三段检查各有用途:connect_timeout=3 保证 MySQL 不可达时 3 秒内报错而不是挂起;conn.ping(reconnect=True) 会在断链时尝试重连一次,验证连接没有被服务端回收;SELECT 1 是数据库界通用的连通性探针,比查版本号更轻。最后打印 collation_server,顺带确认服务端字符集是 utf8mb4 系,避免客户端写了 utf8mb4、服务端却是 latin1,导致存进库里的中文变形。
自检通过后再处理两个高频坑:一是执行大批量写入报 1153 或 2006,那是 max_allowed_packet 太小,改用 executemany 分批并把单批控制在几千行以内;二是程序空闲一段时间后第一次查询特别慢,那是 wait_timeout 断链后重连的正常代价,连接池里把 ping 打开就能缓解。把这些检查写进部署脚本,Python3 连 MySQL 这层基本不会再出暗故障。
本文还有配套的精品资源,点击获取