资讯详情

资讯详情

python和mysql交互操作实例详解【基于pymysql库】

前言「Python 和 MySQL 交互」这句话听起来抽象落到代码上其实就是四步装驱动 → 建连接 → 用游标发 SQL → 提交事务。本文全部基于 PyMySQL 这个纯 Python 驱动来讲因为它在任何平台上都不需要编译环境装完就能跑。有两个高频误解要先说清楚。第一个「PyMySQL 和 MySQLdb 是一回事」。不是。MySQLdb是mysqlclient这个 C 扩展提供的模块名属于另一条技术路线。PyMySQL 提供了一个兼容开关pymysql.install_as_MySQLdb()作用是把自己注册成MySQLdb模块名让那些写着import MySQLdb的老代码不用改就能跑起来。这只是名字兼容不是实现相同——它不会让 PyMySQL 变成一个 C 扩展。第二个「执行完 SQL 就生效了」。不是。DB-API 规范下连接的默认行为是手动提交INSERT/UPDATE/DELETE执行完不提交就等于没做。这一条是本文反复强调的重点。下面按接口逐个给可用示例。所有 SQL 都用%s占位符传参不做字符串拼接。一、安装、连接与连接自检python -m pip install PyMySQL# 适用于 Python 3.9PyMySQL 1.ximport osimport pymysqlconn pymysql.connect(hostos.environ.get(MYSQL_HOST, 127.0.0.1),port3306,useros.environ[MYSQL_USER],passwordos.environ[MYSQL_PASSWORD],databaseos.environ[MYSQL_DB],charsetutf8mb4,cursorclasspymysql.cursors.DictCursor,connect_timeout5,)# 连接可用性可以这样快速验证print(服务端版本:, conn.server_version)print(连接字符集:, conn.charset)conn.close()PyMySQL 1.0 起connect()的参数是仅关键字参数位置传参会直接抛TypeError。同时db/passwd这两个老别名已经废弃正式写法是database和password。老代码迁移时先全局搜一遍这两个词。连接对象的几个常用能力方法 / 属性作用注意点cursor(cursorNone)创建游标可传游标类不传就用连接建立时的cursorclasscommit()提交当前事务不提交则写操作不生效rollback()回滚当前事务异常路径必须调用close()关闭连接关闭前会隐式回滚未提交的事务ping(reconnectTrue)探活可自动重连重连会丢掉未提交的事务select_db(db)切换当前库参数是库名不能是用户输入autocommit(value)开关自动提交默认False二、游标执行 SQL 的唯一入口PyMySQL 把「发语句」和「取结果」都放在游标上。execute()的第一个参数是 SQL第二个是参数序列只有当你确定这条 SQL 里真的没有可变部分时才可以省略第二个参数。# 适用于 Python 3.9with conn.cursor() as cur:# 有参数走占位符驱动负责转义与类型转换cur.execute(SELECT id, title, price FROM book WHERE price %s AND author %s,(30.0, 刘慈欣),)# 无参数DDL 或没有可变部分的查询cur.execute(SELECT COUNT(*) AS n FROM book)取结果的四件套cur.fetchone()—— 取下一行没有则返回None不是抛异常。cur.fetchmany(size)—— 取最多size行返回列表。cur.fetchall()—— 取剩余全部行返回列表。for row in cur:—— 迭代游标等价于反复fetchone()。同一个游标的结果集只能顺次消费一次。fetchall()之后再fetchone()只会拿到None因为它已经把结果读完了。要重新读得重新execute。三、CRUD 完整实例下面这段在一个脚本里跑通增删改查可以直接复制执行前提是库和账号已存在且有建表权限。# 适用于 Python 3.9import pymysqlconn pymysql.connect(host127.0.0.1,userapp,passwordapp_password,databasedemo,charsetutf8mb4,cursorclasspymysql.cursors.DictCursor,autocommitFalse,)try:with conn.cursor() as cur:cur.execute(CREATE TABLE IF NOT EXISTS book (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(200) NOT NULL,author VARCHAR(100) NOT NULL,price DECIMAL(10,2) NOT NULL,UNIQUE KEY uk_title (title)) ENGINEInnoDB DEFAULT CHARSETutf8mb4)# 增单条cur.execute(INSERT INTO book (title, author, price) VALUES (%s, %s, %s),(三体, 刘慈欣, 23.00),)print(新增 id , cur.lastrowid)# 增批量用 executemany别在 Python 里循环 executecur.executemany(INSERT INTO book (title, author, price) VALUES (%s, %s, %s),[(活着, 余华, 28.50),(许三观卖血记, 余华, 26.00),],)# 查条件值走占位符cur.execute(SELECT id, title, price FROM book WHERE author %s ORDER BY price,(余华,),)for row in cur:print(row[id], row[title], row[price])# 改cur.execute(UPDATE book SET price %s WHERE title %s,(25.00, 三体),)print(更新行数 , cur.rowcount)# 删cur.execute(DELETE FROM book WHERE title %s, (三体,))print(删除行数 , cur.rowcount)conn.commit()except Exception:conn.rollback()raisefinally:conn.close()三个容易忽略的点cur.lastrowid对应 MySQL 的LAST_INSERT_ID()只在插入后有意义。executemany之后它的值不确定不要依赖。cur.rowcount是受影响行数。MySQL 的UPDATE在「新值和旧值相同」时默认返回 0而不是 1判断「记录是否存在」不能只看它。CREATE TABLE IF NOT EXISTSUNIQUE KEY是脚本可重复执行的保障。第二次跑不会因为表已存在或数据重复而炸掉。四、事务的三种写法写法一全手动最通用# 适用于 Python 3.9try:with conn.cursor() as cur:cur.execute(UPDATE wallet SET balance balance - %s WHERE id %s, (30, 1))cur.execute(UPDATE wallet SET balance balance %s WHERE id %s, (30, 2))conn.commit()except Exception:conn.rollback()raise写法二用连接的上下文管理器PyMySQL 的连接对象支持with退出时提交抛异常时回滚。注意它不会关闭连接连接还得自己关。# 适用于 Python 3.9with conn:with conn.cursor() as cur:cur.execute(UPDATE wallet SET balance balance - %s WHERE id %s, (30, 1))cur.execute(UPDATE wallet SET balance balance %s WHERE id %s, (30, 2))conn.close() # with 只管事务不管连接生命周期写法三全程自动提交# 适用于 Python 3.9conn pymysql.connect(host127.0.0.1, userapp, passwordapp_password,databasedemo, charsetutf8mb4, autocommitTrue,)只在「每条语句独立成事务」确实成立时才这么写。一旦有多条写操作需要「同生共死」自动提交就把原子性丢掉了。五、常见报错对照与定位PyMySQL 的异常树完全遵循 DB-APIMySQLError是根下面分Error→InterfaceError/DatabaseErrorDatabaseError再分出DataError、OperationalError、IntegrityError、InternalError、ProgrammingError、NotSupportedError。所以捕获pymysql.err.OperationalError就能拿到连接类、SQL 执行类的错误比捕获宽泛的Exception精确得多。错误码含义常见成因1045Access denied for user用户名或密码错或该用户没有从当前主机连接的权限1049Unknown database库名拼错或库没建1146Table doesnt exist表名错或连到了另一个库1054Unknown column列名拼错或 SQL 里引用了结果集中不存在的别名1062Duplicate entry唯一键/主键冲突1064SQL 语法错误SQL 拼写问题或占位符个数与参数不匹配1366Incorrect string value连接或列的字符集不是utf8mb4存入的字符超出范围2003连不上服务器客户端侧服务未启动、端口不通、防火墙拦截定位思路1045 / 1049 / 2003 属于连接层检查配置和网络1146 / 1054 / 1064 属于 SQL 层检查语句1062 / 1366 属于数据层检查约束和字符集。分层之后排查速度会快很多。常见坑点1.connect()用位置参数❌pymysql.connect(127.0.0.1, app, pwd, demo)—— PyMySQL 1.0 起参数是仅关键字直接报TypeError。✅ 全部写成关键字pymysql.connect(host127.0.0.1, userapp, passwordpwd, databasedemo)。2.fetchall()之后再fetchone()❌rows cur.fetchall(); first cur.fetchone()——first是None因为结果集已经读完。✅ 想拿两遍就存下来rows[0]或者重新execute。3. 拼接 SQL 字符串❌cur.execute(SELECT * FROM book WHERE author ... user_input)—— 把用户输入直接拼进语句输入里的引号就能改变语句结构。✅cur.execute(SELECT * FROM book WHERE author %s, (name,))。4. 单元素参数写成字符串❌cur.execute(... WHERE id %s, 12)—— 驱动会把它当成字符序列逐个绑定报参数个数不匹配。✅cur.execute(... WHERE id %s, (12,))。5. 异常被吞掉连接留在失败事务里❌try: ... except: pass—— 后续所有操作都报错看起来像「数据库坏了」。✅except Exception: conn.rollback(); raise。6. 用with conn:就当连接会自动关❌with conn: ...之后不调用conn.close()长期运行的服务连接数只增不减。✅with conn:只负责提交/回滚连接生命周期自己用try/finally管。7. 用rowcount判断记录是否存在❌cur.execute(UPDATE ... SET v %s WHERE id %s, (v, i)); print(cur.rowcount 1)—— 新旧值相同时 MySQL 返回 0。✅ 先SELECT判断存在性或者用SELECT ROW_COUNT()前先确认、或者比较受影响行数的语义差异。8. 用import MySQLdb后又想切 PyMySQL只改了 import 名❌import pymysql as MySQLdb就以为万事大吉但原代码里用了MySQLdb.cursors.SSDictCursor之类并不完全对应的名字。✅ 老代码迁移用官方开关import pymysql; pymysql.install_as_MySQLdb()但这只是模块名兼容行为差异仍需逐个核对。总结环节结论安装pip install PyMySQL纯 Python无编译依赖连接参数仅关键字用database/passwordcharsetutf8mb4游标execute(sql, params)结果集只能顺次消费一次事务默认手动提交with conn:管事务不管连接异常按 DB-API 分层捕获按错误码分连接层/SQL 层/数据层定位安全值一律占位符标识符用白名单账号最小权限PyMySQL 的 API 面很小真正决定代码是否可靠的只有两件事所有值都走占位符每一条异常路径都回滚。把这两条固化成习惯剩下的只是查文档的事。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →