错误 2006:MySQL 服务器已消失

The*_*uhn 8 mysql python uwsgi

我正在使用 uWSGI 和 nginx 在 CentOS 服务器上运行 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...
Run Code Online (Sandbox Code Playgroud)

重要说明: 此错误不是由 MySQL 的 wait_timeout 引起的。去过也做过。

我想知道这个问题是否是由同时提供并发请求引起的。我让自己成为一个穷人的负载测试员:

for i in {1..10}; do (curl -o /dev/null http://domain.com &); done;
Run Code Online (Sandbox Code Playgroud)

果然,在这十个请求中,至少有一个会抛出 2006 错误,通常更多。有时错误会变得更奇怪,例如:

sqlalchemy.exc.NoSuchColumnError: "Could not locate column in row for column 'table.id'"
Run Code Online (Sandbox Code Playgroud)

当该列绝对存在并且在所有其他相同请求上工作正常时。或者,这个:

sqlalchemy.exc.ResourceClosedError: This result object does not return rows. It has been closed automatically.
Run Code Online (Sandbox Code Playgroud)

再一次,它对所有其他请求都能正常工作。

为了进一步验证问题是否源于并发数据库连接,我将 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
Run Code Online (Sandbox Code Playgroud)

重度谷歌搜索发现的错误很少,但建议我增加 max_allowed_pa​​cket。我将它增加到 100M 并重新启动 MySQL,但这根本没有帮助。

总结: 并发连接到 MySQL 会导致2006, 'MySQL server has gone away'和其他一些奇怪的错误。MySQL 的错误日志中没有任何相关性。

我已经为此工作了几个小时,但没有取得任何进展。有人可以帮我吗?

Fli*_*ick 18

我也遇到过这个问题,找到了原因并解决了。

发生这种情况的原因是 python uwsgi 插件(或更可能是所有 uwsgi 插件)在应用程序加载到父级后 fork() 新工作人员。结果,子级继承了父级的所有资源(包括文件描述符,如 db 连接)。

您可以在uwsgi wiki上简要阅读:

uWSGI 会尽可能在写入时滥用 fork() 复制。默认情况下,它会在加载您的应用程序后分叉。如果您不希望这种行为,请使用 --lazy 选项。启用它,将指示 uWSGI 在每个工人的 fork() 之后加载应用程序

您可能知道,除非您明确保护它们,否则 Python 的 mysqldb 连接和游标不是线程安全的。因此,多个进程(例如 uwsgi 工作人员)同时使用相同的 mysql 连接/游标会破坏它。

在我的情况下(对于King Arthur's Gold API),当我在另一个模块的范围内为每个请求创建 MySQL 连接时,这工作正常,但是当我想要持久连接来帮助提高性能时,我将数据库连接和游标移动到全局范围父模块。结果,我的人脉像你一样互相影响。

对此的解决方法是将“lazy”关键字(或 --lazy 命令行选项)添加到您的 uwsgi 配置中。结果,应用程序将为每个子进程重新分叉,而不是从父进程分叉并共享连接(并在某个时候踩到它,这样 MySQL 服务器会由于某个时候的损坏请求而强制关闭它)。

最后,如果您想在不修改 uwsgi 配置的情况下执行此操作,您可以使用 @postfork 装饰器在工作进程分叉后立即正确创建新的数据库连接。你可以在这里阅读。

我从你的跟进中看到你已经切换到 pgsql,但这里是答案,所以你可以在晚上睡得更好,对于像你这样的人,我试图找到答案!

PS 一旦我了解了这个问题(由于工人相互踩踏而导致光标损坏)但没有意识到 fork() 和 --lazy 的一点,我正在考虑在工人将“在全局范围内从池中检出 mysql 连接,然后在退出 application() 之前“检入”,但是使用 --lazy 可能要好得多,除非您的 Web/应用程序负载变化足够大以至于您不断创造新的工人。即便如此,我可能更喜欢 --lazy,因为它比实现自己的数据库连接池要干净得多。

编辑:这里是这个问题+解决方案的更详尽的文章,因为对于遇到它的其他人来说,它的信息不足:http : //tns.u13.net/?p=190