![错误 2006:MySQL 服务器已消失](https://linux22.com/image/589919/%E9%94%99%E8%AF%AF%202006%EF%BC%9AMySQL%20%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%B7%B2%E6%B6%88%E5%A4%B1.png)
我在 CentOS 服务器上使用 uWSGI 和 nginx 运行 Python Pyramid 应用。我使用 SQLAlchemy 作为 ORM、MySQLdb 作为 API 以及 MySQL 作为数据库。该网站尚未上线,因此唯一的流量来自我和公司的其他一些员工。我们购买了一些数据来填充数据库,因此最大的(也是最常查询的)表大约有 150,000 行。
昨天,我快速连续打开了网站的四个新标签,结果出现了几个 502 Bad Gateway 错误。我查看了 uWSGI 日志,发现了以下内容:
sqlalchemy.exc.OperationalError: (OperationalError) (2006, 'MySQL server has gone away') 'SELECT ge...
重要的提示: 此错误不是由 MySQL 的 wait_timeout 引起的。我遇到过这种情况,也遇到过类似情况。
我想知道这个问题是否是由同时处理的并发请求引起的。我给自己做了一个穷人的负载测试器:
for i in {1..10}; do (curl -o /dev/null http://domain.com &); done;
果然,在这 10 个请求中,至少有一个会抛出 2006 年错误,通常还会更多。有时错误会变得更加奇怪,例如:
sqlalchemy.exc.NoSuchColumnError: "Could not locate column in row for column 'table.id'"
当该列确实存在,并且在所有其他相同请求上都能正常工作时。或者,这个:
sqlalchemy.exc.ResourceClosedError: This result object does not return rows. It has been closed automatically.
再次,它对所有其它请求都运行良好。
为了进一步验证问题是否源自并发数据库连接,我将 uWSGI 设置为单个工作器并禁用多线程,强制一次处理一个请求。果然,问题消失了。
为了找到问题所在,我为 MySQL 设置了一个错误日志。除了 MySQL 启动时的一些提示外,它仍然是空的。
这是我的 MySQL 配置:
[mysqld]
default-storage-engine = myisam
key_buffer = 1M
query_cache_size = 1M
query_cache_limit = 128k
max_connections=25
thread_cache=1
skip-innodb
query_cache_min_res_unit=0
tmp_table_size = 1M
max_heap_table_size = 1M
table_cache=256
concurrent_insert=2
max_allowed_packet = 1M
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 64K
innodb_file_per_table=1
log-error=/var/log/mysql/error.log
在错误上进行大量 Google 搜索后,发现没有什么结果,但建议我增加 max_allowed_packet。我将其增加到 100M 并重新启动 MySQL,但这没有任何帮助。
总结一下: MySQL 的并发连接会导致2006, 'MySQL server has gone away'
一些其他奇怪的错误。MySQL 的错误日志中没有任何相关信息。
我已经为此工作了好几个小时,但没有任何进展。有人能帮我吗?
答案1
我也遇到过这个问题,并找到了原因和解决办法。
发生这种情况的原因是,python uwsgi 插件(或更可能是所有 uwsgi 插件)在父级中加载应用程序后 fork() 新工作程序。因此,子级从父级继承所有资源(包括文件描述符,如数据库连接)。
您可以在uwsgi 维基:
uWSGI 会尽可能地滥用 fork() 写时复制。默认情况下,它会在加载应用程序后进行 fork。如果您不希望出现这种情况,请使用 --lazy 选项。启用此选项将指示 uWSGI 在每个 worker 的 fork() 之后加载应用程序
您可能知道,除非您明确保护 Python 的 mysqldb 连接和游标,否则它们不是线程安全的。因此,多个进程(例如 uwsgi 工作进程)同时使用同一个 mysql 连接/游标会损坏它。
就我而言(对于亚瑟王的黄金API) 当我在另一个模块的作用域中按请求创建 MySQL 连接时,此方法运行良好,但当我想要持久连接来帮助提高性能时,我将数据库连接和光标移动到父模块中的全局作用域。结果,我的连接就像您的一样互相干扰。
解决这个问题的方法是将“lazy”关键字(或 --lazy 命令行选项)添加到您的 uwsgi 配置中。这样,应用程序将为每个子应用程序重新分叉,而不是从父应用程序分叉并共享连接(并在某个时候踩踏它,这样 MySQL 服务器就会由于某个时候的损坏请求而强制关闭它)。
最后,如果你想要一种不修改 uwsgi 配置的方式来做到这一点,那么你可以在工作进程分叉后立即使用 @postfork 装饰器正确地创建新的数据库连接。你可以阅读有关这里。
我从你的后续行动中看到你已经切换到 pgsql,但这里是答案,这样你晚上就可以睡得更好,并且对于像你和我一样试图找到这个问题答案的任何人来说!
PS 当我理解了问题(由于 worker 相互踩踏导致光标损坏)但并没有意识到 fork() 和 --lazy 的问题时,我考虑实现自己的池,其中 worker 将从全局范围的池中“签出”mysql 连接,然后在退出 application() 之前“重新签入”,但是除非您的 web/应用程序负载变化很大,以至于您不断创建新的 worker,否则使用 --lazy 可能更好。即便如此,我可能更喜欢 --lazy,因为它比实现您自己的数据库连接池要干净得多。
编辑:这里有关于这个问题+解决方案的更详细的描述,因为对于遇到这个问题的其他人来说,这方面的信息不足:http://tns.u13.net/?p=190