Mysql中的too many connections问题背后

  张一帆   2019年09月23日


  今天,在工作中遇到了mysql报too many connections的问题。于是,在百度上快速的搜到了解决方法,只要在数据库上操作flush hosts操作就行了。我按照这个提示操作了一遍确实可以了。于是,一旦出现问题,我优先会去做此操作,以至于我只要打开数据库就会先来一遍这个操作,反正做了会降低出问题的概率。正当我这样做的不亦乐乎之时,大问题出现了。

  在中秋节前的那个周二,是我们公司上线的窗口期。在晚上6点左右的时候,同事告诉我50那台机子挂了,测试数据库也在这台机子上,所以也挂掉了。我们上线前QA是需要在测试线回归一下问题后才能上线的,但是由于测试数据库挂了,导致QA的测试不能够进行,时间太短又来不及去迁移整个数据库,看来只能求助OP同学了。后来,OP同学告诉我们50这台服务器是我们的自运维机器,OP们也不知道如何操作(他们其实是不愿意管)。好说歹说OP才腾出功夫将服务器重启了(后来得知,其实并没有重启,而是50这台服务器是虚机,因为某些原道导致机器卡住了,于是虚机就被暂存起来了,然而OP做的就是吧暂存的虚机重新打开就行了),这样看似一切正常的操作,突然mysql就出现了too many connections的报错,这回我是根本连不上mysql了。经过询问DBA,这种情况是数据库太多的僵尸链接导致的,僵尸链接没有放开连接,导致后边的连接根本请求不到资源。这下问题就来了,我没有root权限(其实知道这台机子因为年代久远的原因没有人知道他的root权限了),那怎么办才能够清除这些僵尸链接呢?好了,前言已经铺垫的很多了,我也确实需要回忆一下我的解决方案和问题背后涉及的原理了。

问题解释

if you get a Too many connections error when you try to connect to the mysqld server, this means that all available connections are in use by other clients.

发生too many connections的错误是因为所有可用的连接都被其他客户端保持着呢。

The number of connections permitted is controlled by the max_connections system variable. The default value is 151 to improve performance when MySQL is used with the Apache Web server. (Previously, the default was 100.) If you need to support more connections, you should set a larger value for this variable.

max_connections定义了被允许连接的数量。默认值是151,151是一个性能较高的值,尤其在web服务上。如果你需要更多的连接数,需要设置更大的数字。

mysqld actually permits max_connections+1 clients to connect. The extra connection is reserved for use by accounts that have the CONNECTION_ADMIN or SUPER privilege. By granting the privilege to administrators and not to normal users (who should not need it), an administrator who also has the PROCESS privilege can connect to the server and use SHOW PROCESSLIST to diagnose problems even if the maximum number of unprivileged clients are connected. See Section 13.7.6.29, “SHOW PROCESSLIST Syntax”. 
 
 mysqld进程使max_connections+1为客户端最大数量,这1个特别的连接是提供给最高用户权限的人用的。最高权限的人可以单独拥有这个链接,以此在发生问题时可以登录到mysql中查看问题所在,通过show processlist命令可以分析问题。也就是说151个连接全部被打满时,仍然有个第152链接等待这个最高权限的人进入。

  上面的引用出自mysql用户手册的原语。这只是告诉了我们在这种错误的情况下我们是可以做出调整的。

mysql> show variables like '%max_con%' \G;
*************************** 1. row ***************************
Variable_name: max_connect_errors
        Value: 100
*************************** 2. row ***************************
Variable_name: max_connections
        Value: 151
*************************** 3. row ***************************
Variable_name: mysqlx_max_connections
        Value: 100
*************************** 4. row ***************************
Variable_name: performance_schema_max_cond_classes
        Value: 100
*************************** 5. row ***************************
Variable_name: performance_schema_max_cond_instances
        Value: -1
5 rows in set (0.00 sec)

解决方法

  当我们拥有mysql的root权限的时候我们可以直接运行命令set global max_connections = xxx

  当我们没有mysql的root权限时我们可以通过重启数据库来解决。还有一种是直接在命令端更改(参见:外国连接),外国人给了一个语句gdb -p $(cat mysqld.pid) -ex "set max_connections=xxx" -batch来操作,这个骚气的操作我暂时没看太懂,gdb怎么就能够操作这个呢?

自己测试

  知道了问题产生的原理了,那么我就自己模拟了一下,看看我能不能复现出这类问题

  首先,就是需要创建出是2个账号,然后再把max_connections设置为1.这样,当A用户连接上后,B去连接就连接不上了,然而我的root却能够登录。如果能够达到这样的效果,就说明了我复现成功了。

  值得注意的是mysql自升到了8.0以后,创建用户后需要紧跟着为用户分配权限。如果不这么做,你是之后再分配权限的话,就会得到You are not allowed to create a user with GRANT的错误提示。

mysql> create user `A`@localhost identified by 'testA';
mysql> grant select on laravel562.* to `test3`@`localhost`; //创建用户后直接分配读权限

Query OK, 0 rows affected (0.00 sec)

  我一共创建了两个用户分别是testA和testB。当我登录testA的时候,我通过root登录后运行```show processlist``后发现

*************************** 1. row ***************************
                 host: locahost
                 user: testA
authentication_string: *94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29
               plugin: mysql_native_password
*************************** 2. row ***************************
                 host: localhost
                 user: mysql.infoschema
authentication_string: $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED
               plugin: caching_sha2_password
*************************** 3. row ***************************
                 host: localhost
                 user: mysql.session
authentication_string: $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED
               plugin: caching_sha2_password
*************************** 4. row ***************************
                 host: localhost
                 user: mysql.sys
authentication_string: $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED
               plugin: caching_sha2_password
*************************** 5. row ***************************
                 host: localhost
                 user: root
authentication_string: *332A08EE56001FE5139E2FB111080AA441242991
               plugin: mysql_native_password

  我们可以发现结果中可以看到testA和root两个用户的连接情况。当我这个时候让testB连接的话,testB就会报too many connections的错误了。 我再让testA关闭掉,再连testB就可以了。

  之后,我删除了testA和testB,应用drop user `test`@`localhost`;