我的Python自学之路程序员

“MySQL server has gone away” in

2017-01-07  本文已影响1106人  rainybowe

原文链接:MySQL-server-has-gone-away-in-django-ThreadPoolExecutor

MySQL server has gone away报错

最近碰到MySQL server has gone away的报错,报错出现的现象是:

项目基本情况:基于python3.5+django1.8,数据库mysql,生产和测试环境都是通过nginx+uwsgi部署

分析原因

谷歌了一下MySQL server has gone away问题可能的原因:

  1. MySQL服务宕了
  2. 连接超时
  3. 进程在server端被主动kill
  4. SQL语句太长

再结合项目实际情况逐条分析:

mysql> show global status like 'uptime';
+---------------+----------+
| Variable_name | Value    |
+---------------+----------+
| Uptime        | 13881221 |
+---------------+----------+
1 row in set (0.00 sec)

报错日志中并没有服务重启的信息,同时uptime值很大,表示已经运行很长时间。因此第一条原因可以排除

mysql> show global status like 'com_kill';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_kill      | 12    |
+---------------+-------+
1 row in set (0.00 sec)

Com_kill居然有这么多-_-||,但也不确定出错的查询语句是否是慢查询。找了一下报错代码的查询语句,属于索引查询,而且查询时间不超过100ms,因此,这一条也可以排除。

mysql> show global variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout  | 86400 |
+---------------+-------+
1 row in set (0.00 sec)

目前设置的最大超时时间是24小时,也就是在这24小时内有数据库连接超时,超时连接后面又被用到,导致报错。

之前在django数据库连接中分析了django的数据库连接是基于线程(thread.local)创建的全局变量,即线程本地变量,下面简称为线程变量。

再结合出错现象分析一下:

看一下ThreadPoolExecutor中创建线程的逻辑:

def _adjust_thread_count(self):
    # When the executor gets lost, the weakref callback will wake up
    # the worker threads.
    def weakref_cb(_, q=self._work_queue):
        q.put(None)
    # TODO(bquinlan): Should avoid creating new threads if there are more
    # idle threads than items in the work queue.
    if len(self._threads) < self._max_workers:
        t = threading.Thread(target=_worker,
                             args=(weakref.ref(self, weakref_cb),
                                   self._work_queue))
        # 线程被设为守护线程
        t.daemon = True
        t.start()
        self._threads.add(t)
        _threads_queues[t] = self._work_queue

线程池中创建的线程属于守护线程,当主线程退出,子线程也会跟着退出。而子线程是在调用submit方法提交异步任务时,若线程池中实际线程数量小于指定数量,便会创建。因此主线程是请求线程。

在用uWSGI部署的django项目中,请求线程是由uWSGI分配的。uWSGI会根据配置文件中的process, threads参数决定开多少工作进程和子线程,同时还有max-requests参数,表示为每个工作进程设置的请求数上限。
当该工作进程请求数达到这个值,就会被回收重用(重启),其子线程也会重启。所以上面的报错现象中,其实是工作进程重启了,请求子线程也会重建,导致线程池中的守护线程也会被kill了,报错就停止了。

总结一下原因

解决方案

要解决这个问题,最直接的办法是在线程池的所有异步任务中,在执行数据库操作之前,检查数据库连接是否可用,然后关掉不可用连接。

from threading import local

def close_old_connections():
    # 获取当前线程本地变量
    connections = local()
    # 根据数据库别名获取数据库连接
    if hasattr(connections, 'default'):
        conn = getattr(connections, 'default')
        # 检查连接可用性,并关闭不可用连接
        conn.close_if_unusable_or_obsolete()

或者改写一下django获取和保存数据库连接的机制,可以创建一个全局的数据库连接池,不管是常规请求还是异步任务,都从连接池获取数据库连接,由连接池保证数据库连接的数量和可用性。



参考阅读
MySQL server has gone away报错原因分析
django数据库连接
WSGI & uwsgi

上一篇下一篇

猜你喜欢

热点阅读