Python操作MySQL数据库时常见的三个陷阱及解决方法

 更新时间:2026年05月31日 08:29:45   作者:detayun  
这段文章详细剖析了pymysql、mysql-connector-python及SQLAlchemy在数据库操作中可能遇到的三个关键问题:异常处理不当导致数据不一致、连接池管理不当引发资源耗尽、以及批量更新操作时事件监听失效,需要的朋友可以参考下

坑一:pymysql —— 异常吞掉,钱扣了没到账

案发现场

import pymysql

conn = pymysql.connect(host='localhost', user='root', password='pwd', database='test')
cursor = conn.cursor()

# 转账:扣款
cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
# 假设这里程序崩溃 / 网络超时 / 抛出异常...
# 加款永远没执行
cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")

conn.commit()  # 第一条已经生效,无法回滚

结果:用户A被扣了100块,用户B没收到钱。数据不一致,且无法挽回。

根因

pymysql 默认 autocommit=False,但每条 execute 本身不会自动提交——问题出在异常发生后,第一条 SQL 已经被 MySQL 隐式提交(因为 pymysql 在某些异常场景下会触发自动提交),或者更常见的情况是:开发者以为异常会自动回滚,但实际上没有显式调用 rollback()

解决方法

try:
    cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
    cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
    conn.commit()
except Exception as e:
    conn.rollback()  # ← 必须显式回滚
    raise  # 或记录日志后重新抛出
finally:
    cursor.close()
    conn.close()

关键原则try 里写业务 SQL,except 里必须有 rollback()finally 里关闭连接。三件套缺一不可。

坑二:mysql-connector-python —— 连接池泄漏,服务跑着跑着就挂了

案发现场

import mysql.connector
from mysql.connector import pooling

dbconfig = {
    "host": "localhost", "user": "root", "password": "pwd", "database": "test"
}
connection_pool = pooling.MySQLConnectionPool(pool_name="mypool", pool_size=5, **dbconfig)

def get_user(user_id):
    conn = connection_pool.get_connection()  # 从池中取连接
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
    result = cursor.fetchall()
    # 忘记关闭连接,直接返回
    return result

结果:前5次请求正常,第6次开始报错 Too many connections。因为连接取走后没归还,池子里的5个连接被永久占用。

根因

mysql-connector-python 的连接池行为是连接常驻——取走后不会自动回收。与 SQLAlchemy 的 QueuePool(空闲超时后销毁连接)不同,这里的连接一旦泄漏,池子容量就永久减少,直到耗尽。

解决方法

def get_user(user_id):
    conn = connection_pool.get_connection()
    try:
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
        return cursor.fetchall()
    finally:
        cursor.close()
        conn.close()  # ← 必须归还连接到池中

或者用上下文管理器:

from contextlib import closing

def get_user(user_id):
    with closing(connection_pool.get_connection()) as conn:
        with conn.cursor() as cursor:
            cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
            return cursor.fetchall()
    # 退出 with 自动关闭,连接归还池中

监控指标:定期执行 SHOW PROCESSLIST,观察 Sleep 状态的连接数是否持续增长。

坑三:SQLAlchemy —— 批量更新时,事件监听器"沉默"了

案发现场

from sqlalchemy import event, update
from sqlalchemy.orm import Session

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    updated_at = Column(DateTime)

# 定义事件:每次更新自动维护 updated_at
@event.listens_for(User, 'before_update')
def set_updated_at(mapper, connection, target):
    target.updated_at = datetime.now()

# 业务代码:批量更新用户状态
session = Session()
stmt = update(User).where(User.status == 'pending').values(status='processed')
session.execute(stmt)  # ← 监听器根本没触发
session.commit()

结果:1000条记录的 status 改了,但 updated_at 全是 NULL。因为 session.execute(stmt) 走的是 Core 层,绕过了 ORM 的对象生命周期,事件监听器根本不知道发生了更新。

根因

SQLAlchemy 的事件系统绑定的是 ORM 对象的状态变化session.execute(update(...)) 是直接执行 SQL,不经过 ORM 对象加载,所以 before_update / after_update 全部静默。

解决方法(三选一)

方案适用场景代码
回归 ORM 遍历更新数据量 < 1万for u in session.query(User).filter_by(status='pending'): u.status='processed'
SQL 里直接写追求性能values(status='processed', updated_at=func.now())
数据库触发器强一致性要求交给 MySQL 层,ON UPDATE CURRENT_TIMESTAMP

推荐大多数场景用方案二,性能和一致性兼顾:

from sqlalchemy import func

stmt = (
    update(User)
    .where(User.status == 'pending')
    .values(status='processed', updated_at=func.now())
)
session.execute(stmt)
session.commit()

三个坑的本质对比

坑一 pymysql坑二 mysql-connector坑三 SQLAlchemy
表面问题异常没回滚连接耗尽事件没触发
真实原因异常处理不完整连接池机制不同Core vs ORM 路径差异
一句话解法except 里必写 rollback()finally 里必关连接批量操作别用 Core 绕过 ORM

这三个坑覆盖了 异常流、资源管理、抽象层边界 三个维度,踩过一次就不会再踩第二次。

以上就是Python操作MySQL数据库时常见的三个陷阱及解决方法的详细内容,更多关于Python操作MySQL数据库常见陷阱的资料请关注脚本之家其它相关文章!

相关文章

  • python利用numpy存取文件案例教程

    python利用numpy存取文件案例教程

    这篇文章主要介绍了python利用numpy存取文件案例教程,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Python Websockets库的使用指南

    Python Websockets库的使用指南

    python websockets库是一个用于创建WebSocket服务器和客户端的Python库,它提供了一种简单的方式来实现实时通信,支持异步和同步操作,并且易于使用,本文给大家介绍了Python Websockets库的使用指南,需要的朋友可以参考下
    2025-04-04
  • 利用Python的Twisted框架实现webshell密码扫描器的教程

    利用Python的Twisted框架实现webshell密码扫描器的教程

    这篇文章主要介绍了利用Python的Twisted框架实现webshell密码扫描器的教程,用到了Twisted框架的异步通信机制,需要的朋友可以参考下
    2015-04-04
  • OpenCV之理解KNN邻近算法k-Nearest Neighbour

    OpenCV之理解KNN邻近算法k-Nearest Neighbour

    这篇文章主要为大家介绍了OpenCV之理解KNN邻近算法k-Nearest Neighbour,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • python函数超时自动退出的实操方法

    python函数超时自动退出的实操方法

    在本篇文章里小编给大家整理的是一篇关于python函数超时自动退出的实操方法,有需要的朋友们可以学习下。
    2020-12-12
  • VSCode设置Playwright的图文教程

    VSCode设置Playwright的图文教程

    本文主要介绍了VSCode设置Playwright的图文教程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-01-01
  • python3模块smtplib实现发送邮件功能

    python3模块smtplib实现发送邮件功能

    这篇文章主要为大家详细介绍了python3模块smtplib实现发送邮件功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • 详解如何优化和调整Python中Scrapy的性能

    详解如何优化和调整Python中Scrapy的性能

    在本篇高级教程中,我们将深入探讨如何优化和调整Scrapy爬虫的性能,以及如何处理更复杂的抓取任务,如登录,处理Cookies和会话,以及避免爬虫被网站识别和封锁,需要的朋友可以参考下
    2023-09-09
  • python实现对图片进行旋转,放缩,裁剪的功能

    python实现对图片进行旋转,放缩,裁剪的功能

    今天小编就为大家分享一篇python实现对图片进行旋转,放缩,裁剪的功能,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-08-08
  • 前女友发来加密的

    前女友发来加密的"520快乐.pdf",我用python破解开之后,却发现

    520收到前女友发来的加密PDF文件,说打开之后有惊喜,难道是要复合?我用python破解开之后,却发现...python干货+剧情满满收藏收藏
    2021-08-08

最新评论