Python操作数据库全解析:pymysql连接、事务与CRUD实战避坑指南
发布时间:2026/10/3 10:50:08 锦皓数字建站

简介《Python从入门到精通》第14章“操作数据库”配套PPT课件面向正在学习Python编程、希望掌握数据库交互技能的初学者和进阶者。内容围绕pymysql与MySQL、SQLite两种数据库展开从连接参数host、user、password、db、charset等的配置到Connection对象与Cursor游标对象的常用方法系统讲解execute执行SQL、commit提交事务、rollback回滚以及fetchone、fetchmany、fetchall等结果集获取方式并配以实际代码示例展示数据插入、查询等操作。课件共1个PPT文件大小约465KB页面设计精简知识点密度高便于课堂教学或自学速览。目前已有1555人学习下载是快速理解Python数据库编程核心概念的高性价比资料。通过学习读者能够独立完成数据库连接、数据操作与事务处理的代码编写为后续Web开发、爬虫等项目打下基础。1. 操作数据库这章到底能让你少踩几个坑很多自学 Python 的人学到文件操作就停了觉得数据库是「后端工程师的事」拿到这份《Python 从入门到精通 第14章 操作数据库》课件时第一反应多半是翻两页就搁置。但我拆完这 25 页 PPT 后想说Python 操作数据库恰恰是入门阶段性价比最高的一章因为它把「写代码」和「真实业务」之间的那道缝补上了。课件内容并不深主线很清晰——先讲 pymysql 怎么连 MySQL再讲 Connection 和 Cursor 这对核心对象各自干什么最后用 SQLite 收尾演示一套完整 CRUD。适合刚学完语法、想把自己的数据持久化存下来的新手也适合准备面试前快速过一遍数据库编程接口的求职者。我接下来会按课件顺序把每一块拆开补上参数说明和实际跑代码时会遇到的那些坑。2. 把数据库接进 Pythonpymysql 连接参数与 Connection/Cursor 对象2.1 connect() 参数不是照着填就完事每个字段都有讲究课件第一章就给了这么一段连接代码看起来平平无奇import pymysql conn pymysql.connect( hostlocalhost, useruser, passwordpasswd, dbtest, charsetutf8, cursorclasspymysql.cursors.DictCursor )这里我建议你不要直接复制而是逐个确认参数含义因为每一行都对应一个实际的故障点。host填localhost表示连接本机 MySQL如果你要连远程数据库这里要改成服务器的 IP 或域名而且大概率还要加一个port3306参数——MySQL 默认端口是 3306但很多云数据库实例用的是自定义端口不写port连不上时你都不知道去哪查。user和password是数据库账号注意这个账号的权限范围新手常见的翻车现场是本地 root 能连换了应用账号就报Access denied原因往往是账号只授权了某个库。db参数指定你要操作的数据库名课件里写的是test你换成自己的库名即可。这里有个非常隐蔽的坑db和database是等价的pymysql 两个都认但如果你在连接时不指定db后面执行 SQL 就必须写成库名.表名的形式否则会报No database selected。charsetutf8是字符集设置注意这里不要写成utf-8带横线的写法 pymysql 不认。而且utf8在 MySQL 里实际是utf8mb3如果你要存 emoji 表情或生僻字得用utf8mb4这是后话避坑章节我会展开。cursorclasspymysql.cursors.DictCursor是容易被忽略但影响深远的一个参数。默认情况下游标返回的数据是元组你要通过row[0]、row[1]这样的下标访问字段改成DictCursor后每一行变成一个字典你可以用row[id]、row[name]这种键名访问。课件选DictCursor是对的代码可读性高很多但你要记住这改变了结果集的访问方式以前写row[0]的代码在DictCursor下会直接报TypeError: tuple indices must be integers。所以连接参数不是抄一遍就完它决定了你后面所有代码的写法。2.2 Connection 对象是「连接」不是「操作入口」课件给 Connection 对象列了四个方法cursor()、commit()、rollback()、close()并配了这样一段使用示例conn pymysql.connect( hostlocalhost, useruser, passwordpasswd, dbtest, charsetutf8, cursorclasspymysql.cursors.DictCursor ) cur conn.cursor() cur.execute(INSERT INTO users (id, name) VALUES (1, mr)) cur.close() conn.commit() conn.close()我拆这段代码时的第一感受是顺序值得注意。很多人第一次写会先commit()再close()这没问题但容易漏掉的是cur.close()。你可能会想连接都关了游标还需要单独关吗需要。游标是数据库会话里的独立资源不关闭它在连接池场景下会造成游标泄漏MySQL 服务端会有对应的临时资源一直挂着。规范顺序是先关游标再提交事务最后关连接。严格说commit()放在cur.close()前后都能生效因为这个事务属于连接而不是游标但养成「用完先关游标」的习惯在写复杂查询时能少很多资源方面的麻烦。Connection对象本质上是客户端与 MySQL 服务器之间的一条会话通道。cursor()是这条通道上开启一个执行 SQL 的工作句柄commit()把事务里的所有更改持久化rollback()撤销自上次提交以来的所有未提交更改close()释放通道。理解这层关系后你就明白为什么commit()没调数据就「离奇消失」了——因为 MySQL 默认开启事务你的INSERT只是在会话里生效没提交就断开连接服务器端直接丢弃。课件把这四个方法列成表看起来是背诵题实际是让你建立「连接-游标-事务」三件套的肌肉记忆。2.3 Cursor 对象的方法清单哪些是高频、哪些是冷门课件把 Cursor 对象的方法列了一张表我帮你按实际使用频率排个序。最高频的是execute()执行一条 SQL可以带参数其次是fetchall()、fetchone()、fetchmany(size)三个取数方法然后是executemany()批量执行最后是callproc()和nextset()这俩属于冷门——callproc()调存储过程中小项目很少用nextset()处理多个结果集只有存储过程或批量 SQL 才可能碰到。cur conn.cursor() # 单条插入注意 execute 返回的是受影响行数 affected cur.execute(INSERT INTO users (id, name) VALUES (%s, %s), (2, python)) # 批量插入executemany 接收一条 SQL 和参数序列 data [(3, a), (4, b), (5, c)] cur.executemany(INSERT INTO users (id, name) VALUES (%s, %s), data) conn.commit() # 查询并取数 cur.execute(SELECT * FROM users) rows cur.fetchall() for row in rows: print(row) cur.close() conn.close()这里有个细节值得拎出来说execute()的返回值是受影响行数而不是查询结果。很多人第一次写result cur.execute(SELECT ...)然后直接print(result)打印出来一个数字以为查询失败了实际这是命中的记录条数。真正要拿数据必须用fetchone()、fetchmany(size)或fetchall()去游标里取。executemany()的第二个参数是一个可迭代对象每个元素是一个参数元组它底层是复用同一条 SQL 模板循环执行比你在 Python 里自己写 for 循环逐条execute()快得多尤其插入上千行时差距非常明显。3. 把 SQL 真正跑起来execute 执行、事务提交与数据抓取3.1 查询数据的三种方式fetchone、fetchmany、fetchall 怎么选课件专门讲了查询数据的三种方式原话很短但展开说这里面的门道不少。fetchone()每次从结果集里取一条记录游标指针自动下移适合逐条处理、内存敏感的场景fetchmany(size)一次取指定数量适合分页或分批消费fetchall()一次取全部适合结果集很小、需要整体操作的场景。cur.execute(SELECT id, name FROM users) # 方式一逐条取循环里处理 row cur.fetchone() while row: print(row) row cur.fetchone() # 方式二按批取每次取 2 条 while True: batch cur.fetchmany(2) if not batch: break print(batch) # 方式三全量取 rows cur.fetchall() print(len(rows))三个方法对应的场景差别很大。如果你用fetchall()去取一张十万行的表Python 会把所有数据一次性加载进内存机器差一点直接卡死这时候应该用fetchmany(1000)分批消费或者干脆fetchone()逐条处理。反过来如果你只需要第一条记录比如判断某个条件是否存在用fetchone()就够了拿fetchall()是浪费。还有一个容易忽略的点游标是流式读取的fetchone()之后再调fetchall()拿到的只是剩余记录而不是从头开始的全量数据。想重新取一遍必须重新execute()。3.2 事务边界commit 和 rollback 是一对谁也别丢课件在 Connection 对象方法里列了commit()和rollback()但没有强调它们的配对关系。实际项目中事务的典型写法是try 里执行一组 SQL全部成功就commit()任何一步异常就rollback()把前面的操作全部撤销。这张「后悔药」只有在事务范围内才有效——如果你每执行一条 SQL 就commit()一次那rollback()就没有任何可回滚的内容了。try: cur conn.cursor() cur.execute(UPDATE users SET name new WHERE id 1) cur.execute(INSERT INTO logs (action) VALUES (update_user)) conn.commit() # 两条 SQL 一起生效 except Exception as e: conn.rollback() # 任何一条失败两条都撤销 print(事务回滚:, e) finally: cur.close() conn.close()这里有个实际业务里非常常见的决策点到底是一条 SQL 一提交还是一个事务包多條 SQL我给出的判断标准是看业务一致性要求。像「更新用户资料后写一条操作日志」这两步必须同时成功或同时失败必须放进同一个事务像「每插入一条商品记录」这种本身就是独立事件的逐条提交反而更灵活不会因为一条脏数据把整批操作全部回滚。课件里的示例代码是单条 INSERT 后直接commit()那是为了演示最基本的写法不要把它当作唯一正确的模式。3.3 参数化查询为什么不能把变量直接拼进 SQL 字符串课件示例里写的是cur.execute(INSERT INTO users (id, name) VALUES (1, mr))值直接写在 SQL 里。入门阶段这样写没问题但一旦数据来自用户输入这就成了 SQL 注入的突破口。安全且规范的做法是参数化查询用%s占位符把实际值作为execute()的第二个参数传入cur conn.cursor() # 错误示范字符串拼接用户输入 name 时可能注入恶意 SQL # cur.execute(INSERT INTO users (id, name) VALUES (1, name )) # 正确做法参数化pymysql 会自动处理转义 name mr; DROP TABLE users; -- cur.execute(INSERT INTO users (id, name) VALUES (%s, %s), (1, name)) conn.commit() cur.close() conn.close()参数化查询的价值不只是防注入它还能帮你规避引号转义的麻烦。比如上面例子里的name变量如果包含单引号拼接字符串的写法轻则语法错误重则被恶意构造 SQL。pymysql 收到带参数的execute()后会把参数值安全地转义再拼进 SQL 发给服务器你完全不用手动处理引号。注意%s是 pymysql 的占位符不要和 Python 字符串格式化的%搞混——这里你传的是一个元组(1, name)不是格式化后的字符串。4. 操作数据库的四个高频坑字符集、事务、批量与连接泄漏4.1 字符集坑utf8 存 emoji 直接报错现象往表里插入带 emoji 的数据比如INSERT INTO users (name) VALUES (程序员)报错Incorrect string value: \xF0\x9F\x98\x84或者插入成功后查出来是乱码。原因课件里的连接参数charsetutf8在 MySQL 里对应的是utf8mb3这个字符集只支持最多 3 字节的 UTF-8 编码而 emoji 是 4 字节编码。建表时如果字段也是utf8存不下这类字符。解决连接参数改成charsetutf8mb4同时把表的字符集也改掉。我的习惯是建表时显式指定CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50) ) DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;如果表已经建好了用ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;转换。注意这里有个连带问题改了连接字符集后之前用utf8存的乱码数据可能显示异常这是因为数据本身已经损坏不是连接参数能救回来的。4.2 事务未提交数据「消失」了现象代码执行了INSERT程序不报错但打开 Navicat 查表数据不在或者程序重启后再查数据还是不在。原因pymysql 默认开启了事务但很多教材示例里只做了execute()没做commit()。MySQL 服务器端的事务还挂着连接一关就被回滚了。解决检查自己的代码是否调用了conn.commit()。我踩过这个坑之后养成了一个习惯——所有写操作INSERT/UPDATE/DELETE之后强制检查两条一是有没有commit()二是commit()的位置在不在所有写操作之后。另外注意conn.close()不会自动提交未完成的事务它只会回滚并释放连接。4.3 DictCursor 与元组游标混用现象设置了cursorclasspymysql.cursors.DictCursor后代码里还用row[0]访问字段报错TypeError: tuple indices must be integers或者没设置DictCursor代码里用row[name]访问报错TypeError: string indices must be integers。原因游标类型决定了结果集里每一行的数据结构。默认是元组DictCursor是字典两种访问方式不能混用。解决先确认连接参数里有没有cursorclass再统一全项目的访问风格。我一般全程用DictCursor因为字段名访问的可读性比下标好得多而且后续如果改了 SELECT 的字段顺序下标访问的代码会静默出错——row[0]可能从id变成了name代码不报错但逻辑错了这种 bug 最难查。4.4 批量插入用错方式性能差到怀疑人生现象往表里插入几千行数据用 for 循环逐条execute()跑了十几秒甚至几十秒换成executemany()后秒完成。原因execute()逐条执行每次都要经过一次完整的「SQL 解析-执行-返回」流程还伴随着网络往返executemany()在底层做了优化批量发送减少了解析次数和网络开销。解决批量操作一律用executemany()。课件里没有展开讲这个方法但它是实际项目里最常见的性能优化手段之一。data [(i, fuser_{i}) for i in range(10000)] cur.executemany(INSERT INTO users (id, name) VALUES (%s, %s), data) conn.commit()注意executemany()的第二个参数是「可迭代的元组序列」不要传成单个元组否则会报参数数量不匹配。我之前翻车就是这样——忘了外面套一层列表直接把(1, a)传进去pymysql 把它当成两条数据去绑定参数直接报错。5. 往 SQLite 扩展用 sqlite3 快速验证你的 CRUD 功底课件后半部分讲了 SQLite它是 Python 内置sqlite3模块直接支持的轻量级数据库——不需要安装服务器不需要账号密码数据就是磁盘上的一个.db文件。操作 MySQL 的整套思路连接、建表、增删改查、事务在 SQLite 上完全通用非常适合拿来练手和验证代码逻辑。import sqlite3 # 连接如果文件不存在会自动创建 conn sqlite3.connect(test.db) cur conn.cursor() # 建表 cur.execute( CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ) ) # 增 cur.execute(INSERT INTO user (id, name) VALUES (?, ?), (1, mr)) # 改 cur.execute(UPDATE user SET name ? WHERE id ?, (python, 1)) # 查 cur.execute(SELECT * FROM user) print(cur.fetchall()) # 删 cur.execute(DELETE FROM user WHERE id ?, (1,)) conn.commit() cur.close() conn.close()这段代码和 pymysql 版本有两点差异值得注意。第一占位符从%s变成了?这是sqlite3模块的规定第二不用传字符集和游标类型SQLite 默认就是 UTF-8返回的行默认是元组。如果你用惯了DictCursor在这里想让行变成字典需要额外指定conn.row_factory sqlite3.Row这样就能用字段名访问了但要转成真正的字典还得套一层dict(row)。我的实际建议是用 SQLite 做练习用 MySQL 跑真业务。在 SQLite 上把建表和增删改查的流程跑通理解游标、提交、回滚这些概念然后切换到 pymysql 时只需要换掉连接代码和占位符风格。这样的学习路径能把「数据库编程」的核心逻辑和「具体数据库的方言」分开思维负担小很多。验证代码是否写对我有一个习惯沿用至今每个 CRUD 操作写完都用一条独立的查询去核对数据。插入后查一次更新后查一次删除后再查一次。不要只看代码不报错就认为操作成功了——不报错只代表语法没毛病不代表数据状态符合预期。就拿UPDATE来说如果WHERE条件没匹配到任何行pymysql 不会报错但execute()返回的受影响行数是 0你完全可以通过这个返回值来判断操作是否真正生效。从那以后我每次写完数据库相关代码都强制走一遍「连接参数确认 → 事务边界确认 → 结果集确认 → 资源关闭确认」这四步已经成了肌肉记忆。这套思路帮我少加了不少班尤其在生产环境出问题时排查顺序清晰不会像无头苍蝇一样乱试。这份课件虽然只有 25 页但把数据库操作的主干都覆盖到了按我上面的路线拆开吃透入门足够了。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。