一、错误原因
这个错误是mysql数据库上报的,django的ORM在进行数据库连接时,数据库连接数超过阈值,导致各种请求都会失败,具体的效果会是
- 所有的请求都会是500错误
- django内置admin页面打开也显示too many connections错误
- 归根结底就是django的数据库连接配置和mysql的配置配合的问题
二、初步排查
一般普通的django项目的连接数很难会出现这种问题,如果真的出现问题,最大的可能性就是你的django项目代码存在缺陷,比如,明明你可以进行批量操作的语句,非要做单条执行;又或者你在每一个请求开始的时候都进行了大量的update操作等等。我们可以按照下面的方式进行排查
1、登录mysql数据库查看连接情况 show processlist;
show processlist;
查看哪些连接时间过长,并且info语句的内容基本相同,通过sql语句反向推测出django的ORM语句,定位到具体的代码段进行优化。
2、登录mysql查看mysql数据库的最大连接数
- 查看mysql数据库配置连接数 max_connections
mysql> show variables like '%conn%'; +-----------------------------------------------+--------------------+ | Variable_name | Value | +-----------------------------------------------+--------------------+ | character_set_connection | utf8mb4 | | collation_connection | utf8mb4_0900_ai_ci | | connect_timeout | 10 | | disconnect_on_expired_password | ON | | init_connect | | | max_connect_errors | 100 | | max_connections | 500 | | max_user_connections | 0 | | mysqlx_connect_timeout | 30 | | mysqlx_max_connections | 100 | | performance_schema_session_connect_attrs_size | 512 | +-----------------------------------------------+--------------------+ 11 rows in set (0.11 sec)
- 查看使用数
mysql> show global status like '%conn%'; +-------------------------------------------------------+---------------------+ | Variable_name | Value | +-------------------------------------------------------+---------------------+ | Aborted_connects | 0 | | Connection_errors_accept | 0 | | Connection_errors_internal | 0 | | Connection_errors_max_connections | 0 | | Connection_errors_peer_address | 0 | | Connection_errors_select | 0 | | Connection_errors_tcpwrap | 0 | | Connections | 915 | | Locked_connects | 0 | | Max_used_connections | 73 | | Max_used_connections_time | 2022-07-11 10:14:36 | | Mysqlx_connection_accept_errors | 0 | | Mysqlx_connection_errors | 0 | | Mysqlx_connections_accepted | 0 | | Mysqlx_connections_closed | 0 | | Mysqlx_connections_rejected | 0 | | Performance_schema_session_connect_attrs_longest_seen | 126 | | Performance_schema_session_connect_attrs_lost | 0 | | Ssl_client_connects | 0 | | Ssl_connect_renegotiates | 0 | | Ssl_finished_connects | 0 | | Threads_connected | 32 | +-------------------------------------------------------+---------------------+ 22 rows in set (0.15 sec)
三、优化处理
1、优化代码
将django定位到的代码段进行ORM操作优化,将for循环类的操作全都改成批量操作
2、修改mysql配置文件
- 临时修改,具体数值自己根据服务器的性能决定,数据库重启后失效
set GLOBAL max_connections=1000; # 总的最大连接数 set GLOBAL max_user_connections=500; # 每个账号最大的连接数 set GLOBAL wait_timeout=500; # 最大空闲时间,会强制释放sleep的链接 set GLOBAL interactive_timeout=500; # 最大检测时间
- 永久修改,修改配置文件
- 查看配置文件路径ps aux|grep mysql|grep ‘my.cnf’ 和 mysql –help|grep ‘my.cnf’
- 修改配置文件
- 重启mysql服务
//查看配置文件的路径 [root@api etc]# ps aux|grep mysql|grep 'my.cnf' //如果没有输出,就查看默认路径配置 [root@api etc]# mysql --help|grep 'my.cnf' order of preference, my.cnf, $MYSQL_TCP_PORT, /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf [root@api etc]# vi /etc/my.cnf //在[mysqld]下增加配置 max_connections=500 max_user_connections=500 wait_timeout=600 interactive_timeout=600 //保存,重启 [root@api etc]systemctl restart mysqld [root@api etc]systemctl status mysqld
3、django数据库配置和mysql配合
django的数据库配置中除了数据库连接的信息之外,还提供了一个额外的参数——"CONN_MAX_AGE"
默认: 0
一个数据库连接的寿命,以秒为整数。使用 0 在每次请求结束时关闭数据库连接——这是 Django 的历史行为,使用 None 则是无限的持久连接。
链接
通俗讲就是一个django和mysql的连接的最大空闲时间,那么对应数据库中的配置就是 wait_timeout 和 interactive_timeout两个参数。
正确的值配置方式应该是
wait_timeout>=interactive_timeout>django中的CON_MAX_AGE
4、修改django的最大连接数配置(其他论坛帖子的解释,没有把问题将清楚,可以参考一下)
在数据库中,有一个参数 CONN_MAX_AGE , 它用来配置 Django 跟数据库的持久化连接。这一项的默认值是0。 Django 中数据库连接的逻辑是,每一个请求结束都会关闭当前的数据库连接。这意味着每来一个新的请求, Django 都会创建一个新的数据库连接。
当配置此项的值时,需要根据参考数据库的 wait_timeout 配置,建议不要大于 wait_timeout 。此外此项还有 None。如果配置为 None,就意味着不限制连接时长。
如果没有配置 CONN_MAX_AGE 就会出现数据库连接数太多,抛出 too many connections 错误的问题,原因就是上面所说的,所以当并发访问量过大来不及关闭连接时,会导致连接数不断增多。
但是需要注意的是,如果你采用多线程的方式部署项目,最好不要配置 CONN_MAX_AGE。因为如果每一个请求都会使用一个新的线程来处理的,那么每个持久化的连接就达不到复用的目的。另外一个经验就是,如果使用gevent 作为worker来运行项目的话,那么也建议不配置 CONN_MAX_AGE。因为 gevent 会给 Python 的 thread(线程模块)动态打补丁(patch),这回导致数据库连接无法复用
# CONN_MAX_AGE的值一定要小于数据库配置的最大连接数
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxx',
'USER': 'xxx',
'PASSWORD': 'xxx',
'HOST': 'xxx',
'PORT': '3306',
'CONN_MAX_AGE': 5*60,
}
}
至此,可以解决绝大部分的场景错误