数据库连接问题:sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2006, "MySQL server has gone away")报错

1、情形:在系统首次进入时,会显示“系统处理异常”的弹框,但是刷新页面后又正常显示了。

2、查看日志部分如下:

in do_execute
    cursor.execute(statement, parameters)
  File "/home/jiafu/.local/lib/python3.6/site-packages/pymysql/cursors.py", line 148, in execute
    result = self._query(query)
  File "/home/jiafu/.local/lib/python3.6/site-packages/pymysql/cursors.py", line 310, in _query
    conn.query(q)
  File "/home/jiafu/.local/lib/python3.6/site-packages/pymysql/connections.py", line 547, in query
    self._execute_command(COMMAND.COM_QUERY, sql)
  File "/home/jiafu/.local/lib/python3.6/site-packages/pymysql/connections.py", line 814, in _execute_command
    self._write_bytes(packet)
  File "/home/jiafu/.local/lib/python3.6/site-packages/pymysql/connections.py", line 760, in _write_bytes
    CR.CR_SERVER_GONE_ERROR, "MySQL server has gone away (%r)" % (e,)
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")
[SQL: SELECT count(alarm_msg.id) AS count_1 
FROM alarm_msg]
(Background on this error at: https://sqlalche.me/e/14/e3q8)
2025-08-16 09:22:27,494 INFO get_alarm_all Session is active: True

 

报错情况: sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2006, "MySQL server has gone away") 是一个非常典型的数据库连接问题。

3、问题根源分析

这个错误的核心意思是:您的 Python 应用程序尝试使用一个已经失效的数据库连接

发生这种情况最常见的原因是 连接超时。具体流程如下:

  1. 建立连接:您的应用程序启动时,SQLAlchemy 的连接池 (engine) 会创建一些与 MySQL 服务器的连接,并把它们“存放”起来以备后用。
  2. 长时间空闲:如果应用程序在一段时间内没有数据库操作,这些“存放”的连接就会一直处于空闲状态。
  3. 服务器关闭连接:MySQL 服务器有一个 wait_timeout 配置(默认通常是 8 小时,但可以被数据库管理员设置得更短,比如几分钟)。当一个连接空闲时间超过这个阈值后,MySQL 服务器会主动单方面断开这个连接,以释放资源。
  4. 应用再次使用:此时,MySQL 服务器认为连接已经关闭,但您的 Python 应用程序并不知道。当一个新的请求(比如您系统首次进入时的查询)到来时,SQLAlchemy 从连接池里取出了这个实际上已经被服务器关闭的“僵尸连接”,并尝试用它来执行 SQL 查询。
  5. 发生错误:查询请求发到一个已经不存在的连接上,于是就收到了 MySQL server has gone away 的错误。
  6. 刷新后恢复:当错误发生后,SQLAlchemy 的连接池会识别到这个连接已损坏,将其丢弃并创建一个新的、有效的连接。所以当您刷新页面,发起第二次请求时,程序获取了新的连接,一切就正常了。

这完美地解释了为什么“首次进入时报错,刷新后就正常”的现象。

4、解决方案

解决这个问题的标准方法是在创建 SQLAlchemy 引擎时,配置 连接池的回收机制。这可以确保连接池不会持有已经超时的连接。

您需要在 create_engine 函数中添加 pool_recycle 参数。这个参数的值(单位为秒)应该小于 MySQL 服务器的 wait_timeout。一个安全且常用的值是 3600(1 小时)。

修改建议 :配置连接池回收

microservices/data_report/db_session.py 文件中,修改 create_engine 的调用:

# 原代码
# engine = create_engine(DB_URI)

# 修改后的代码
# 增加 pool_recycle=3600 参数,让连接池每隔1小时回收一次空闲连接
engine = create_engine(DB_URI, pool_recycle=3600) 

修改后完整代码:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from contextlib import contextmanager

# 假设 database_type 和 database_info 的获取逻辑在这里
# ...

if database_type == "mysql":
    # Mysql数据库
    HOSTNAME = database_info.get("host_name")
    PORT = database_info.get("port")
    DATABASE = database_info.get("database_name")
    USERNAME = database_info.get("username")
    PASSWORD = database_info.get("password")

    DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{db}".format(
        username=USERNAME,
        password=PASSWORD,
        host=HOSTNAME,
        port=PORT,
        db=DATABASE,
    )
else:
    DB_URI = "sqlite:///microservices/data_report/alarm_msg.db"

# --- 主要修改点 ---
# 添加 pool_recycle=3600 来防止连接超时
engine = create_engine(DB_URI, pool_recycle=3600, echo=False) 

Base = declarative_base()
DBSession = sessionmaker(bind=engine)
session = scoped_session(DBSession)

@contextmanager
def session_scope():
    """
    提供一个事务性的作用域,并确保会话在使用后被正确移除。 
    这是使用 scoped_session 的标准模式。
    """
    s = session() # 从 scoped_session 代理获取当前线程的 Session 实例
    try:
        yield s
        s.commit()
    except Exception as e:
        s.rollback()
        raise e
    finally:
        session.remove() # 在 scoped_session 代理上调用 remove()

 

这个改动会让 SQLAlchemy 自动处理空闲超时的连接,从根本上解决 gone away 的问题。

总结

  1. 核心原因:MySQL 服务器因连接空闲而主动断开了连接,但客户端(SQLAlchemy 连接池)不知道,导致使用了失效连接。
  2. 核心解决方案:在 create_engine 中设置 pool_recycle=3600,强制连接池定期回收并重建空闲连接。



回答来源于AI。

posted @ 2025-08-16 11:13  Tutu007  阅读(184)  评论(0)    收藏  举报