资讯详情

Python操作数据库实战:从sqlite3到MySQL的避坑指南

📅 2026/10/12 1:03:02 | 华诺云谱 👁 阅读
Python操作数据库实战:从sqlite3到MySQL的避坑指南
简介这份课件面向正在系统学习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 / MariaDBPyMySQL 或 mysql-connector-python纯 Python 实现装起来干净占位符用%s不是?多表、要迁移、要 ORMSQLAlchemy统一 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( host127.0.0.1, port3306, userapp, passwordyour_password, databasetest_db, charsetutf8mb4, # 关键支持 emoji 和完整中文 autocommitFalse, # 关键默认手动提交事务边界清晰 cursorclasspymysql.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 或生僻字会直接报错或变问号这是血泪经验。autocommitFalse意味着你必须自己commit好处是能一次提交多条、出错能回滚坏处是忘了 commit 数据就「消失」了。占位符从?变成%s这是 PyMySQL 的约定写错会报TypeError。3. 增删改查落地把课件里的四行代码写成能用的函数3.1 封装连接与游标避免到处复制粘贴课件里每个操作都重新connect一次真实项目里这样写会很快失控。我一般封成一个上下文管理器import sqlite3 from contextlib import contextmanager DB_PATH demo.db contextmanager def get_conn(db_pathDB_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 除非你开autocommitTrue。现象是SELECT能查到换个进程再查就空了。解决方式就是上面get_conn那种写法把 commit 收进统一出口业务代码不用记。4.2 现象插入中文变问号或报编码错 → 原因连接字符集不是 utf8mb4 → 解决建库建表连接三处都统一MySQL 里字符集要在三个地方一致建库时CHARACTER SET utf8mb4、建表时同样、连接时charsetutf8mb4。只改一处另外两处还是旧字符集照样出问题。排查方法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的占位符是?或命名占位符:namePyMySQL 是%s。把%s写进 sqlite3 会报参数数量不匹配把?写进 PyMySQL 会报语法错误。排查时先看报错行再确认当前用的是哪个库。5. 进阶技巧用参数化查询和连接复用把脚本变成能上线的工具5.1 参数化查询不只是防注入还影响执行计划很多人以为参数化查询只是为了安全其实它还有一个作用让数据库复用执行计划。当你用字符串拼接时每条 SQL 文本都不同数据库每次都要重新解析用占位符时SQL 模板固定只有参数变数据库可以缓存执行计划。数据量大时这个差异会体现在响应时间上。# 不推荐每次 SQL 文本都不同 name 张三 cur.execute(fSELECT * 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( host127.0.0.1, userapp, passwordyour_password, databasetest_db, charsetutf8mb4, autocommitTrue ) return _conn逻辑说明_conn.open检查连接是否还活着断了就重连。参数说明autocommitTrue适合读多写少、单条操作的场景如果是批量写还是建议手动事务。注意这个写法在多线程下不安全多线程要用连接池。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 页的内容才算真正变成你自己的东西。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑