Django并发情况下数据库操作异常、连接数过多、游标问题等 解决方案

使用 siege 对项目接口进行并发测试时,后台日志出现下面几种异常:

  1. _io.BufferedReader
  2. Packet sequence number wrong
  3. NoneType' object has no attribute 'settimeout'
  4. Too many connections


运行环境:

ubuntu 16.04

python 3.6

Django 2.14

MySQL 5.6

部署 Gunicorn

数据库包 Pymysql


问题分析:

  1. Django 数据库访问默认是长连接,并发情况下连接数耗尽
  2. 使用 pymysql 库,性能较差,使用gevent 时 会出现游标访问异常等问题

解决方案:

  1. 使用连接池管理
  2. 使用MySQLdb库


1.使用PooledDB库是实现连接池

import MySQLdb
import threading
from dbutils.pooled_db import PooledDB
import logging
log = logging.getLogger("django")
# 用连接池来返回数据库连接
class DMysqlPoolConn:
    def __init__(self, config):
       self.__pool = PooledDB(**config)
    def get_conn(self):
        return self.__pool.connection()
class MysqlPoolInstance(object):
    # 线程锁
    _instance_lock = threading.Lock()
    def __init__(self, *args,**kwargs):
    @classmethod
    def get_storage_instance(cls, connect_params):
        if not hasattr(MysqlPoolInstance,'_instance'):
            with MysqlPoolInstance._instance_lock:
                MysqlPoolInstance._instance = DMysqlPoolConn(connect_params)
        return MysqlPoolInstance._instance


2.重写数据库引擎

from django.db.backends.mysql.base import DatabaseWrapper as _DatabaseWrapper
import MySQLdb
class DatabaseWrapper(_DatabaseWrapper):
    # 使用Pool连接池
    def get_new_connection(self, conn_params):
        conn_params['creator'] = MySQLdb
        g_pool_connection = MysqlPoolInstance.get_storage_instance(conn_params)
        conn = g_pool_connection.get_conn()
        return conn
    # 覆盖掉原来的close方法,查询结束后连接不会自动关闭
    def _close(self):
        return None
    # 重写父类方法为空方法,因为PooledDB的conn没有autocommit属性,不重写就会报错
    def _set_autocommit(self, autocommit):


3. settings.py中设置 ENGINE, CONN_MAX_AGE需要设置为None

需要删除 这句环境兼容代码(如果有的话,否则无法使用MySQLdb)

import pymysql

pymysql.install_as_MySQLdb()

######################################
# 数据库配置
######################################
DATABASES = {
    'default': {
        'ENGINE': 'core.mysql_engine',  # 数据库引擎
        'NAME': CommonConfig.database_name,  # 你要存储数据的库名,事先要创建
        'USER': CommonConfig.database_user,  # 数据库用户名
        'PASSWORD': CommonConfig.database_password,  # 密码
        'HOST': CommonConfig.database_host,  # 主机
        'PORT': CommonConfig.database_port,  # 数据库使用的端口
        'CONN_MAX_AGE': None,
        'OPTIONS': {
            'charset': 'utf8mb4',