简介:这份课件面向正在系统学习Python编程、希望掌握数据库操作技能的初学者与进阶开发者,是《Python从入门到精通》系列课程的第14章配套PPT。内容围绕Python数据库编程接口展开,重点讲解pymysql模块连接MySQL的完整流程,包括连接对象参数配置、Connection与Cursor对象的核心方法、事务提交与回滚机制,并延伸至SQLite轻量级数据库的建库建表与增删改查操作。资源包内含1个PPT文件,大小约465KB,以图文与代码示例结合的方式呈现,便于课堂演示与自学对照。目前已有1555人学习下载,适合需要快速理清数据库连接管理、游标使用与SQL执行思路的读者,可作为课程复习与动手实践的参考材料。
1. 从一份 25 页课件说起:Python 操作数据库到底要跨过几道坎
很多人第一次接触「Python 操作数据库」,是在一份 25 页的课件里看到import sqlite3、cursor.execute()、conn.commit()三行代码,然后以为这事就完了。真到项目里,你会发现课件没告诉你:连接什么时候关、批量插入为什么慢得离谱、参数化查询到底防住了什么、MySQL 和 SQLite 的占位符为什么长得不一样。这份课件标题叫「Python从入门到精通 第14章 操作数据库」,它想解决的核心问题其实只有一个——让 Python 程序把数据稳定地写进数据库、再稳定地读出来。适合谁看?刚学完 Python 基础语法、准备做第一个带持久化存储的小工具的人;也适合写了半年脚本、每次连数据库都靠复制粘贴、从没认真想过连接池和事务边界的人。下面我不复述课件,而是把这一章背后真正要落地的东西拆开讲。
2. 选库与建连:sqlite3、PyMySQL、SQLAlchemy 该怎么挑
2.1 三种典型场景对应的库选型
课件里最常见的是sqlite3,因为它是 Python 标准库,不用装任何东西。但选型不能只看「能不能跑」,要看数据放哪、几个人用、并发多高。
| 场景 | 推荐库 | 理由 | 注意点 |
|---|---|---|---|
| 本地单文件工具、脚本缓存 | sqlite3 | 标准库自带,零配置,单文件即数据库 | 写并发差,不适合多进程同时写 |
| 连公司 MySQL / MariaDB | PyMySQL 或 mysql-connector-python | 纯 Python 实现,装起来干净 | 占位符用%s,不是? |
| 多表、要迁移、要 ORM | SQLAlchemy | 统一 API,换库成本低 | 学习曲线陡,别一上来就上 |
我一般会这样判断:如果数据只在本机、只有我自己读写,直接sqlite3;只要涉及网络、多人、权限,就上 MySQL 系;如果表结构会频繁改、还要写迁移脚本,才考虑 SQLAlchemy。课件往往只讲第一种,但真实工作里第二种才是主流。
2.2 用 sqlite3 跑通最小可复现例子
先看能直接抄的代码,这段在本地任意目录都能跑:
import sqlite3 # 连接(文件不存在会自动创建) conn = sqlite3.connect("demo.db") # 让查询结果支持按列名取值,默认是元组 conn.row_factory = sqlite3.Row cur = conn.cursor() # 建表:IF NOT EXISTS 保证重复执行不报错 cur.execute(""" CREATE TABLE IF NOT EXISTS student ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, score REAL DEFAULT 0 ) """) # 参数化插入,? 是 sqlite3 的占位符 cur.execute("INSERT INTO student (name, score) VALUES (?, ?)", ("张三", 92.5)) cur.execute("INSERT INTO student (name, score) VALUES (?, ?)", ("李四", 88.0)) conn.commit() # 不 commit,数据不会真正落盘 cur.execute("SELECT id, name, score FROM student ORDER BY score DESC") for row in cur.fetchall(): print(row["id"], row["name"], row["score"]) cur.close() conn.close()逻辑说明:connect建立连接,cursor是执行 SQL 的句柄,execute执行单条,commit提交事务,fetchall取回全部结果。参数说明:row_factory = sqlite3.Row让结果可以按列名访问,比记下标可靠;?是占位符,值通过第二个参数元组传入,不要用字符串拼接,否则就是 SQL 注入的入口。AUTOINCREMENT只在INTEGER PRIMARY KEY上生效,别乱加。
2.3 连 MySQL 时最容易翻车的两个参数
换成 MySQL,代码结构几乎一样,但有两个参数课件基本不提:
import pymysql conn = pymysql.connect( host="127.0.0.1", port=3306, user="app", password="your_password", database="test_db", charset="utf8mb4", # 关键:支持 emoji 和完整中文 autocommit=False, # 关键:默认手动提交,事务边界清晰 cursorclass=pymysql.cursors.DictCursor # 结果按字典返回 ) cur = conn.cursor() cur.execute("INSERT INTO student (name, score) VALUES (%s, %s)", ("王五", 76.0)) conn.commit() cur.close() conn.close()charset不写utf8mb4,存 emoji 或生僻字会直接报错或变问号,这是血泪经验。autocommit=False意味着你必须自己commit,好处是能一次提交多条、出错能回滚;坏处是忘了 commit 数据就「消失」了。占位符从?变成%s,这是 PyMySQL 的约定,写错会报TypeError。
3. 增删改查落地:把课件里的四行代码写成能用的函数
3.1 封装连接与游标,避免到处复制粘贴
课件里每个操作都重新connect一次,真实项目里这样写会很快失控。我一般封成一个上下文管理器:
import sqlite3 from contextlib import contextmanager DB_PATH = "demo.db" @contextmanager def get_conn(db_path=DB_PATH): conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row try: yield conn conn.commit() # 正常结束自动提交 except Exception: conn.rollback() # 出错自动回滚 raise finally: conn.close() # 无论如何都关连接 # 使用 with get_conn() as conn: conn.execute("INSERT INTO student (name, score) VALUES (?, ?)", ("赵六", 81.0))逻辑说明:@contextmanager把「开连接—提交/回滚—关连接」这套固定动作收进一个函数,业务代码只关心 SQL。参数说明:yield之前是进入 with 块时执行,之后是退出时执行;rollback保证异常时数据不半写。这样写的好处是,任何一处抛异常,连接都会被正确释放,不会留下悬空连接。
3.2 增删改查四个动作的标准写法
# 增:批量插入用 executemany,比循环 execute 快一个数量级 rows = [("钱七", 70.0), ("孙八", 65.5), ("周九", 90.0)] with get_conn() as conn: conn.executemany("INSERT INTO student (name, score) VALUES (?, ?)", rows) # 查:带条件查询,参数照样走占位符 with get_conn() as conn: cur = conn.execute("SELECT name, score FROM student WHERE score >= ?", (80,)) for r in cur.fetchall(): print(r["name"], r["score"]) # 改:UPDATE 一定要带 WHERE,否则全表被改 with get_conn() as conn: conn.execute("UPDATE student SET score = ? WHERE name = ?", (95.0, "张三")) # 删:同样必须带 WHERE with get_conn() as conn: conn.execute("DELETE FROM student WHERE name = ?", ("孙八",))逻辑说明:executemany把多条插入合并成一次网络往返,批量场景下差距非常明显。参数说明:WHERE score >= ?里的?对应元组(80,),注意单元素元组要带逗号,否则会被当成整数。改和删不带WHERE就是全表操作,这是新手最常见的翻车点,没有后悔药。
3.3 事务边界:什么时候该 commit,什么时候该 rollback
事务不是「写完就提交」这么简单。比如转账场景,扣款和入账必须在一个事务里:
with get_conn() as conn: conn.execute("UPDATE account SET balance = balance - ? WHERE id = ?", (100, 1)) conn.execute("UPDATE account SET balance = balance + ? WHERE id = ?", (100, 2)) # 两条都成功,with 退出时统一 commit;任一条抛异常,整体 rollback逻辑说明:with块内两条 SQL 属于同一事务,要么都生效,要么都不生效。参数说明:这里没有手动commit,因为上下文管理器在正常退出时提交。如果业务要求「部分成功也要保留」,那就要拆成两个事务,但那种需求本身要谨慎评估。
4. 避坑与排查:课件不会告诉你的五个真实故障
4.1 现象:程序跑完数据没了 → 原因:忘了 commit → 解决:用上下文管理器统一提交
这是最高频的问题。sqlite3和 PyMySQL 默认都不会自动提交(PyMySQL 除非你开autocommit=True)。现象是SELECT能查到,换个进程再查就空了。解决方式就是上面get_conn那种写法,把 commit 收进统一出口,业务代码不用记。
4.2 现象:插入中文变问号或报编码错 → 原因:连接字符集不是 utf8mb4 → 解决:建库建表连接三处都统一
MySQL 里字符集要在三个地方一致:建库时CHARACTER SET utf8mb4、建表时同样、连接时charset="utf8mb4"。只改一处,另外两处还是旧字符集,照样出问题。排查方法:SHOW VARIABLES LIKE 'character_set%';看服务端,再看连接参数。
4.3 现象:批量插入几千条慢到无法接受 → 原因:循环单条 execute 且每条都 commit → 解决:executemany + 单次提交
循环里每条execute后跟一个commit,等于每条都走一次磁盘同步,几千条就是几千次。改成executemany并只在最后提交一次,速度通常能提升一个数量级。如果数据量特别大,还可以分批,比如每 1000 条提交一次,兼顾内存和速度。
4.4 现象:报database is locked→ 原因:SQLite 多进程/多线程同时写 → 解决:串行化写入或换库
SQLite 同一时刻只允许一个写事务。多进程同时写就会锁冲突。解决方式:要么把写操作集中到一个进程串行处理,要么加timeout参数等待,要么直接换 MySQL。这不是代码 bug,是 SQLite 的设计边界,硬扛没用。
4.5 现象:占位符写错报 TypeError → 原因:sqlite3 用?,PyMySQL 用%s→ 解决:按库区分,别混用
sqlite3的占位符是?或命名占位符:name;PyMySQL 是%s。把%s写进 sqlite3 会报参数数量不匹配,把?写进 PyMySQL 会报语法错误。排查时先看报错行,再确认当前用的是哪个库。
5. 进阶技巧:用参数化查询和连接复用把脚本变成能上线的工具
5.1 参数化查询不只是防注入,还影响执行计划
很多人以为参数化查询只是为了安全,其实它还有一个作用:让数据库复用执行计划。当你用字符串拼接时,每条 SQL 文本都不同,数据库每次都要重新解析;用占位符时,SQL 模板固定,只有参数变,数据库可以缓存执行计划。数据量大时,这个差异会体现在响应时间上。
# 不推荐:每次 SQL 文本都不同 name = "张三" cur.execute(f"SELECT * FROM student WHERE name = '{name}'") # 推荐:模板固定,参数分离 cur.execute("SELECT * FROM student WHERE name = ?", (name,))参数说明:第二种写法里,?是模板的一部分,(name,)是参数,数据库能识别出这是同一条 SQL 的不同参数。第一种写法不仅有注入风险,还让执行计划无法复用。
5.2 连接复用:别每次查询都 connect 一次
短脚本无所谓,但如果是常驻服务或循环里频繁查询,每次connect开销很大。常见做法是维护一个连接对象,或者用连接池。SQLAlchemy 自带池,PyMySQL 可以配合DBUtils做池化。我一般会这样处理:
# 简单场景:模块级单连接,配合重连检查 import pymysql _conn = None def get_conn(): global _conn if _conn is None or not _conn.open: _conn = pymysql.connect( host="127.0.0.1", user="app", password="your_password", database="test_db", charset="utf8mb4", autocommit=True ) return _conn逻辑说明:_conn.open检查连接是否还活着,断了就重连。参数说明:autocommit=True适合读多写少、单条操作的场景;如果是批量写,还是建议手动事务。注意这个写法在多线程下不安全,多线程要用连接池。
5.3 验证方法:用一条 SQL 确认数据真的落盘了
写完代码别只看程序输出,要独立验证。最直接的方式是另开一个终端,用命令行客户端查:
# SQLite sqlite3 demo.db "SELECT COUNT(*) FROM student;" # MySQL mysql -u app -p test_db -e "SELECT COUNT(*) FROM student;"如果程序里查到 3 条,命令行查到 0 条,那基本就是没 commit。这个习惯能帮你快速定位「数据到底写没写进去」这类玄学问题。
5.4 一个具体技巧:把表结构变更写成可重复执行的脚本
课件通常只教建表一次。真实项目里表结构会变,我习惯把变更写成幂等脚本,重复执行不报错:
with get_conn() as conn: # 加字段前先判断是否存在,SQLite 用 PRAGMA cols = [r["name"] for r in conn.execute("PRAGMA table_info(student)")] if "class_name" not in cols: conn.execute("ALTER TABLE student ADD COLUMN class_name TEXT")逻辑说明:PRAGMA table_info返回表的列信息,先查再加,避免重复执行报「duplicate column」。参数说明:MySQL 里对应的是SHOW COLUMNS FROM student或查information_schema。这个技巧能让你把变更脚本放进部署流程,而不是每次手动改。
我自己踩过最深的坑,是早期写脚本时每条插入都 commit,几千条数据跑了十几分钟,后来改成executemany加单次提交,同样的数据几秒钟就完了。从那以后我养成了一个习惯:任何写数据库的代码,先问自己三个问题——事务边界在哪、占位符对不对、连接有没有关。这三个问题答清楚了,课件里那 25 页的内容才算真正变成你自己的东西。希望帮到你。
本文还有配套的精品资源,点击获取