MySQL 服务器已消失 - 通过结帐事件处理程序的断开连接处理不起作用

Posted

技术标签:

【中文标题】MySQL 服务器已消失 - 通过结帐事件处理程序的断开连接处理不起作用【英文标题】:MySQL server has gone away - Disconnect handling via checkout event handler doesn't work 【发布时间】:2011-12-16 07:08:56 【问题描述】:

更新 3/4:

我已经进行了一些测试,并证明使用 checkout 事件处理程序来检查断开连接与 Elixir 一起工作。 开始认为我的问题与从子进程调用 session.commit() 有关吗? 更新:我刚刚通过在子进程中调用 session.commit() 来反驳自己,下面更新了示例.我正在使用多处理模块来创建子进程。

这是显示它应该如何工作的代码(甚至不使用pool_recycle!):

from sqlalchemy import exc
from sqlalchemy import event
from sqlalchemy.pool import Pool
from elixir import *
import multiprocessing as mp

class SubProcess(mp.Process):
    def run(self):
        a3 = TestModel(name="monkey")
        session.commit()

class TestModel(Entity):
    name = Field(String(255))

@event.listens_for(Pool, "checkout")
def ping_connection(dbapi_connection, connection_record, connection_proxy):
    cursor = dbapi_connection.cursor()
    try:
        cursor.execute("SELECT 1")
    except:
        # optional - dispose the whole pool
        # instead of invalidating one at a time
        # connection_proxy._pool.dispose()

        # raise DisconnectionError - pool will try
        # connecting again up to three times before raising.
        raise exc.DisconnectionError()
    cursor.close()

from sqlalchemy import create_engine
metadata.bind = create_engine("mysql://foo:bar@localhost/some_db", echo_pool=True)
setup_all(True)

subP = SubProcess()

a1 = TestModel(name='foo')
session.commit()

# pool size is now three.

print "Restart the server"
raw_input()

subP.start()

#a2 = TestModel(name='bar')
#session.commit()

更新 2:

我不得不寻找另一个解决方案,因为 MySQL-python 的 1.2.2 后版本不再支持重新连接参数。有人有解决方案吗? :\

更新 1(旧解决方案,不适用于 MySQL-python 版本 > 1.2.2):

找到了解决方案:将connect_args='reconnect':True 传递给create_engine 调用解决了问题,自动重新连接。甚至似乎不需要结帐事件处理程序。

因此,在问题的示例中:

metadata.bind = create_engine("mysql://foo:bar@localhost/db_name", pool_size=100, pool_recycle=3600, connect_args='reconnect':True)

原问题:

为这个问题做了相当多的谷歌搜索,但似乎没有找到特定于 Elixir 的解决方案 - 我正在尝试使用 SQLAlchemy 文档中的“Disconnect Handling - Pessimistic”示例来处理 MySQL 断开连接。但是,当我对此进行测试时(通过重新启动 MySQL 服务器),在我的结帐事件处理程序之前引发了“MySQL 服务器已消失”错误。

这是我用来初始化 elixir 的代码:

##### Initialize elixir/SQLAlchemy
# Disconnect handling
from sqlalchemy import exc
from sqlalchemy import event
from sqlalchemy.pool import Pool

@event.listens_for(Pool, "checkout")
def ping_connection(dbapi_connection, connection_record, connection_proxy):
    logging.debug("***********ping_connection**************")
    cursor = dbapi_connection.cursor()
    try:
        cursor.execute("SELECT 1")
    except:
        logging.debug("######## DISCONNECTION ERROR #########")            
        # optional - dispose the whole pool
        # instead of invalidating one at a time
        # connection_proxy._pool.dispose()

        # raise DisconnectionError - pool will try
        # connecting again up to three times before raising.
        raise exc.DisconnectionError()
    cursor.close()

metadata.bind= create_engine("mysql://foo:bar@localhost/db_name", pool_size=100, pool_recycle=3600)

setup_all()

我创建了 elixir 实体对象并使用session.commit() 保存它们,在此期间我看到从上面定义的事件生成的“ping_connection”消息。但是,当我重新启动 mysql 服务器并再次对其进行测试时,它会在 ping 连接事件之前出现 mysql 服务器已消失消息。

这是从相关行开始的堆栈跟踪:

  File "/usr/local/lib/python2.6/dist-packages/elixir/entity.py", line 1135, in get_by
    return cls.query.filter_by(*args, **kwargs).first()
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1963, in first
    ret = list(self[0:1])
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1857, in __getitem__
    return list(res)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 2032, in __iter__
    return self._execute_and_instances(context)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 2047, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1399, in execute
    params)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1532, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1640, in _execute_context
    context)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1633, in _execute_context
    context)
  File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/engine/default.py", line 330, in do_execute
    cursor.execute(statement, parameters)
  File "/usr/lib/pymodules/python2.6/MySQLdb/cursors.py", line 166, in execute
    self.errorhandler(self, exc, value)
  File "/usr/lib/pymodules/python2.6/MySQLdb/connections.py", line 35, in defaulterrorhandler
    raise errorclass, errorvalue
OperationalError: (OperationalError) (2006, 'MySQL server has gone away') 

【问题讨论】:

谨慎使用reconnect 参数,因为它的支持不是标准的,甚至不能正常工作。有关详细信息,请参阅此答案:***.com/questions/207981/… @DenisOtkidach 感谢您的提示,这令人担忧,将确保更彻底地测试重新连接方案。 【参考方案1】:

您是否在两个(mysqld 重新启动之前和之后)操作中使用相同的会话?如果是这样,"checkout" 事件仅在新事务启动时发生。当您调用commit() 时,新事务将启动(除非您使用自动提交模式)并签出连接。所以你在 结帐后重新启动 mysqld。

在第二次操作之前(以及重新启动 mysqld 之后)调用 commit()rollback() 的简单 hack 应该可以解决您的问题。否则,每次在上次提交后等待很长时间时,请考虑使用新的新会话。

【讨论】:

我必须说我真的不知道会话对象是如何工作的,因为我只是潜入 Elixir 来构建我的模型。 setup_all() 是否创建会话?以及如何重用会话?重用会话是否与重用池中的连接相同? @ronalddddd,默认情况下,Elixir 中的实体使用全局 elixir.sessionsetup_all() 根本不使用会话。抱歉,我关于使用新会话的建议是针对纯 SQLAlchemy,它与 Elixir 使用的 Active Record 模式不匹配。但是commit()/rollback() hack 无论如何应该可以工作。 在任何记录操作之前尝试调用 session.commit(),没有运气。现在我不得不寻找另一个解决方案,因为 MySQL-python 的 1.2.2 后版本不再支持重新连接参数。【参考方案2】:

我不确定这是否与我遇到的问题相同,但这里是:

遇到MySQL server has gone away时,我用create_engine(..., pool_recycle=3600)解决了,见http://www.sqlalchemy.org/docs/dialects/mysql.html#connection-timeouts

【讨论】:

我什至尝试设置 pool_recycle=3 仍然无法正常工作:\【参考方案3】:

最后的解决方法是在操作和加载 elixir 实体之前在方法的开头调用 session.remove()。它的作用是将连接返回到池,这样当它再次使用时,池的 checkout 事件将被触发,我们的处理程序将检测到断开连接。 From SQLAlchemy docs:

在请求结束时删除会话不是绝对必要的 - 其他选项包括在结束时调用 Session.close()、Session.rollback()、Session.commit() 以便现有会话返回其连接到池中并删除任何现有的事务上下文。如果单个控制器方法负责确保在请求结束后没有任何事务保持打开状态,那么什么也不做也是一种选择。

非常重要的一小部分信息,我希望它在 elixir 文档中被提及。但是我猜它假设了 SQLAlchemy 的先验知识?

【讨论】:

【参考方案4】:

实际问题是每次调用 sessionmaker 工厂时 sqlalchemy 都会为您提供相同的会话。因此,只要您没有在会话上调用session.remove(),就可能会使用更早打开的会话执行稍后的查询。但是,每次请求会话时都必须记住调用 remove() 并不好玩,sqlalchemy 提供了一个更简单的东西:上下文“范围”会话。

要创建一个作用域会话,只需包装你的 sessionmaker:

from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session(sessionmaker())

这样您每次调用工厂时都会获得上下文绑定会话,这意味着一旦调用函数退出,sqlalchemy 就会为您调用session.remove()。见这里:sqlalchemy - lifespan of a contextual session

【讨论】:

以上是关于MySQL 服务器已消失 - 通过结帐事件处理程序的断开连接处理不起作用的主要内容,如果未能解决你的问题,请参考以下文章

MySQL导入导致“服务器已消失”错误[重复]

Flask-SQLAlchemy 错误 MySQL 服务器已消失

导入大型 sql 文件时 MySQL 服务器已消失

导入大型 sql 文件时 MySQL 服务器已消失

导入大型 sql 文件时 MySQL 服务器已消失

导入大型 sql 文件时 MySQL 服务器已消失