ARTICLE DETAIL

资讯详情

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

Python编程:MySQLdb模块更新数据库获取影响行数实战指南

Python编程:MySQLdb模块更新数据库获取影响行数实战指南 1. 为什么 UPDATE 之后拿不到影响行数用 Python 操作 MySQL很多人第一次写更新逻辑时都会卡在同一个地方SQL 明明执行了数据库里的数据也变了但代码里就是不知道到底改了几行。有人去翻cursor对象看到fetchone()、fetchall()这些方法结果对 UPDATE 语句调用它们直接报错因为更新语句根本不返回结果集。真正能告诉你「这次操作影响了几行」的是cursor.rowcount这个属性。它返回的是上一次execute()之后受影响的行数对 UPDATE、DELETE、INSERT 都有效。问题在于很多人拿到rowcount的值是-1或者0于是开始怀疑是不是模块坏了。其实大多数情况是连接配置、事务提交或者 SQL 条件本身的问题。这篇就围绕 Python MySQLdb 这套组合把「执行 UPDATE/DELETE 后准确拿到影响行数」这件事拆开讲。适合已经会写基本 Python、正在用 MySQL 做数据维护、批量更新或者后台管理功能的同学。你会看到完整的连接配置、参数化 SQL 写法、事务提交与回滚以及怎么验证rowcount到底对不对。整套代码可以直接复制到本地跑改一下库名和表名就能用。需要说明的是MySQLdb 是 MySQL-python 的包名在 Python 3 环境下更常见的是它的继任者mysqlclient导入时依然写import MySQLdb。下面所有示例在mysqlclient上同样成立安装命令我会在配置章节给出。2. 前置准备装好驱动并确认能连上库2.1 安装 MySQLdbmysqlclient在 Python 3 里直接pip install MySQLdb是装不上的包名不对。正确做法是安装mysqlclientpip install mysqlclient如果你用的是 macOS可能会在编译阶段报mysql_config not found这时候先装 MySQL 客户端开发库brew install mysql-client export PATH/opt/homebrew/opt/mysql-client/bin:$PATH pip install mysqlclientUbuntu/Debian 下则是sudo apt-get install python3-dev default-libmysqlclient-dev build-essential pip install mysqlclient装完之后在 Python 里执行import MySQLdb不报错就说明驱动就绪。2.2 准备一张测试表为了后面能验证影响行数先建一张简单的表并塞几条数据CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32), age INT, sex VARCHAR(8) ); INSERT INTO student (name, age, sex) VALUES (张三, 18, man), (李四, 20, man), (王五, 22, woman), (赵六, 19, man);这张表里sexman的有 3 条后面用 UPDATE 批量加年龄时预期影响行数就是 3。2.3 连接配置的推荐写法连接参数建议单独抽出来别硬编码在业务逻辑里。一个可用的连接骨架import MySQLdb conn MySQLdb.connect( host127.0.0.1, port3306, usertestuser, passwdtest123, dbTESTDB, charsetutf8mb4, )这里有几个点值得注意。charset一定要显式指定utf8mb4否则中文可能变问号。host用127.0.0.1比localhost更稳因为localhost在某些系统上会走 socket 连接配置不一致时容易连不上。另外 MySQLdb 默认不是自动提交的这一点直接决定了你后面rowcount能不能拿到正确值下一节会重点讲。3. 可复制配置UPDATE 与 rowcount 的完整骨架3.1 最基础的更新并读取影响行数先看一个最小可运行版本把「执行更新 → 拿 rowcount → 提交」这条链路走通import MySQLdb conn MySQLdb.connect( host127.0.0.1, port3306, usertestuser, passwdtest123, dbTESTDB, charsetutf8mb4, ) cursor conn.cursor() sql UPDATE student SET age age 1 WHERE sex %s affected cursor.execute(sql, (man,)) print(execute 返回值:, affected) print(cursor.rowcount:, cursor.rowcount) conn.commit() cursor.close() conn.close()运行后你会看到两个值都是 3。这里有个容易忽略的细节cursor.execute()本身的返回值就是受影响行数和cursor.rowcount是同一个东西。所以你可以直接用execute的返回值也可以事后读rowcount两者等价。3.2 参数化 SQL 才是正确姿势上面用了%s占位符这是 MySQLdb 的参数化写法。千万不要用字符串拼接# 错误示范别这么写 sql UPDATE student SET age age 1 WHERE sex %s % sex拼接不仅容易出语法错误还有 SQL 注入风险。参数化写法把值和语句分开传给驱动驱动会做转义。注意 MySQLdb 的占位符是%s不管字段是字符串还是数字都用%s不要写成?那是别的驱动的写法。批量更新时可以用executemany()sql UPDATE student SET age %s WHERE id %s rows [(21, 1), (23, 2), (25, 3)] affected cursor.executemany(sql, rows) print(批量影响行数:, cursor.rowcount) conn.commit()executemany的rowcount返回的是所有语句影响行数的总和。实测下来3 条 UPDATE 各改 1 行rowcount就是 3。3.3 事务提交与异常回滚MySQLdb 默认关闭自动提交也就是说你执行完 UPDATE 之后如果不commit()数据不会真正落库而且换个连接查是看不到变化的。这一点和rowcount直接相关在提交之前rowcount反映的是本次事务内的影响行数提交之后才成为持久状态。一个带异常回滚的完整写法import MySQLdb conn MySQLdb.connect( host127.0.0.1, port3306, usertestuser, passwdtest123, dbTESTDB, charsetutf8mb4, ) try: cursor conn.cursor() sql UPDATE student SET age age 1 WHERE sex %s affected cursor.execute(sql, (man,)) print(本次影响行数:, cursor.rowcount) if affected 0: print(没有匹配到任何行回滚) conn.rollback() else: conn.commit() print(提交成功) except MySQLdb.Error as e: conn.rollback() print(出错已回滚:, e) finally: cursor.close() conn.close()这里把commit和rollback放在 try/except 里是生产代码的基本要求。如果更新过程中抛异常回滚能保证数据一致性同时你也不会误以为更新成功了。3.4 想开自动提交怎么办如果你确实需要每条语句立即生效可以在连接时加autocommitTrueconn MySQLdb.connect( host127.0.0.1, port3306, usertestuser, passwdtest123, dbTESTDB, charsetutf8mb4, autocommitTrue, )开了自动提交后就不用再手动commit()rowcount在execute返回时就已经是最终值。但要注意自动提交下没法用rollback()撤销做批量操作时风险更高建议还是手动控制事务。4. 验证请求确认 rowcount 真的准4.1 用 SELECT 交叉验证光看rowcount不够最好用查询对一遍。执行更新前先数一遍符合条件的行数cursor.execute(SELECT COUNT(*) FROM student WHERE sex %s, (man,)) before cursor.fetchone()[0] print(更新前 man 行数:, before) affected cursor.execute(UPDATE student SET age age 1 WHERE sex %s, (man,)) print(rowcount:, cursor.rowcount) print(是否一致:, before cursor.rowcount) conn.commit()预期输出里before和rowcount应该相等。如果不等说明 SQL 条件或者事务状态有问题下一节会讲常见原因。4.2 验证「更新值没变」时的行为MySQL 有个特性如果 UPDATE 的字段值和新值一样默认情况下这一行不算「被改变」rowcount可能返回 0。比如把 age 从 20 改成 20cursor.execute(UPDATE student SET age 20 WHERE id 2) print(值未变化时的 rowcount:, cursor.rowcount)在默认配置下这个值可能是 0因为 MySQL 认为没有实际修改。如果你希望「匹配到就算影响」需要在连接时加client_flagMySQLdb.constants.CLIENT.FOUND_ROWSimport MySQLdb from MySQLdb.constants import CLIENT conn MySQLdb.connect( host127.0.0.1, port3306, usertestuser, passwdtest123, dbTESTDB, charsetutf8mb4, client_flagCLIENT.FOUND_ROWS, )加上这个标志后只要 WHERE 匹配到行rowcount就会计入不管值有没有变。这个坑我在做数据同步时踩过当时一直以为更新失败了其实是值本来就一样。4.3 预期输出对照表场景SQL 条件预期 rowcount匹配 3 行且值有变化WHERE sexman3匹配 0 行WHERE sexunknown0匹配 3 行但值未变默认SET ageage0匹配 3 行但值未变FOUND_ROWSSET ageage3批量 executemany 3 条各改 1 行3把这张表对着跑一遍基本就能摸清rowcount的脾气。5. 本篇常见错排查5.1 rowcount 返回 -1最常见的原因是用了不支持rowcount的语句或者游标类型不对。比如对SELECT之后立刻读rowcount在结果集还没取完时可能不准。另外如果你用的是SSCursor流式游标rowcount在取完所有行之前也是 -1。解决办法是改用默认游标conn.cursor()或者先把结果fetchall()再读rowcount。5.2 更新成功但 rowcount 是 0先确认 WHERE 条件是不是真的匹配到了行。用同样的条件跑一次SELECT COUNT(*)对比。如果 SELECT 有结果而 UPDATE 的rowcount是 0大概率是「值没变化」那个特性参考 4.2 加FOUND_ROWS。还有一种可能是字段类型不匹配比如拿字符串去比数字MySQL 做了隐式转换导致没匹配上。5.3 数据没落库换连接查不到这是没提交事务。MySQLdb 默认不自动提交execute之后必须commit()。如果你在同一个连接里查能看到变化换个连接就看不到基本就是这个原因。检查代码里有没有漏掉conn.commit()或者异常分支里误走了rollback()。5.4 中文写入变问号连接时没指定charsetutf8mb4。补上这个参数同时确认数据库和表的字符集也是utf8mb4。如果表本身是latin1光改连接参数不够需要改表结构。5.5 连接报 2003 或 10452003 是连不上服务检查 host、port 和 MySQL 服务是否启动。1045 是账号密码错误确认用户名密码以及该用户是否有从当前 host 连接的权限。MySQL 的用户权限是userhost绑定的testuserlocalhost和testuser%是两回事。6. 把 rowcount 用进真实业务cursor.rowcount看起来只是个小属性但在实际业务里很有用。比如后台的批量删除功能删完之后要告诉用户「成功删除 N 条」这个 N 就来自rowcount。再比如数据同步任务每次更新后记录影响行数行数为 0 时可以跳过后续处理省掉不必要的计算。如果你在写长期运行的编码任务或者 Agent 类程序需要频繁和模型交互来生成、调试这类数据库代码可以考虑用 Coding Plan 来管理调用额度配合接入文档把 key 配好日常开发会顺很多。模型对话入口适合临时验证一段 SQL 逻辑API Keys 页面则用来生成和管理接入凭证。整套流程走下来核心就三件事连接时配对参数、执行时用参数化 SQL、提交前读rowcount。把这三步固定成模板以后写任何更新逻辑都不会再为「到底改了几行」发愁。
返回列表