某试点现场,测试人员反馈,连接数据库失败,日志出现too many connections
。由于数据库是我用docker部署的,且时间紧急,需立即响应,经询问,版本、配置与其它试点无差别,只是新试点多了一些连接的微服务。
经查,可以在容器启动时添加连接参数解决,如下:
command: ['mysqld', '--max-connections=1024', ‘其它参数’]
为不影响上线,当时提供修改后的配置给测试人员,修改后测试正常。由于这块暂时没有研究到,所以有了本文。
docker-compose.yml 文件:
version: '2'
services:
ttmysql:
image: mysql:8.0
container_name: llmysql
restart: always
command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--explicit_defaults_for_timestamp=false', '--lower_case_table_names=1', '--default_authentication_plugin=mysql_native_password'] ## , '--skip-ssl'
volumes:
- ./mysqldata:/var/lib/mysql
environment:
- TZ=Asia/Shanghai
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_USER=latelee
- MYSQL_PASSWORD=123456
ports:
- "43306:3306"
networks:
- mysql-net
networks:
mysql-net:
driver: bridge
~
为简单起见,数据库的root
用户密码为123456
。
启动:
docker-compose up -d
进入容器:
docker exec -it llmysql bash
查看默认配置文件:
# cat /etc/mysql/my.cnf
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
secure-file-priv= NULL
# Custom config should go here
!includedir /etc/mysql/conf.d/
可以看到,自定义的配置文件应放置于/etc/mysql/conf.d/
目录。查看之:
# ls /etc/mysql/conf.d/
docker.cnf mysql.cnf
其中,/etc/mysql/conf.d/mysql.cnf
文件无实质内容。
对于容器部署的情形,可以将自定义配置文件挂载到上述目录,示例如下:
volumes:
- ./mysqldata:/var/lib/mysql
- ./my.cnf:/etc/mysql/conf.d/my.cnf
由官方文档知,对于系统变量(server-system-variables
),既可以通过命令行参数的形式传递,也可以通过配置文件的形式。从笔者经验看,如大部分使用默认配置,只改动个别参数,则前者较好,但后者定制能力强,适用于高阶应用。
如设置最大连接数,可在my.cnf
文件做如下修改:
[mysqld]
max_connections=1000
也可用命令行方式,参数如下:
command: ['mysqld', '--max-connections=1024', ‘其它参数’]
注意,两者同时存在时,经测试,命令行方式优先级最高。
使用如下命令连接数据库:
mysql -h 127.0.0.1 -P 43306 -uroot -p123456 --ssl-mode=DISABLED
临时 创建test数据库:
create database test;
使用如下命令查询mysql服务器的最大连接数:
show variables like '%max_connections%';
连接及查询输出日志:
$ mysql -h 127.0.0.1 -P 43306 -uroot -p123456 --ssl-mode=DISABLED
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 216676
Server version: 8.0.21 MySQL Community Server - GPL
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show variables like '%max_connections%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| max_connections | 151 |
| mysqlx_max_connections | 100 |
+------------------------+-------+
2 rows in set (3.08 sec)
可以看到,默认最大连接数为151。据官方文档,max_connections
值范围为1~100000。
max_used_connections
:
show global status like 'max_used_connections';
输出示例:
mysql> show global status like 'max_used_connections';
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| Max_used_connections | 1 |
+----------------------+-------+
1 row in set (0.00 sec)
SHOW STATUS LIKE 'Threads%';
输出示例:
mysql> SHOW STATUS LIKE 'Threads%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 0 |
| Threads_connected | 1 |
| Threads_created | 1 |
| Threads_running | 2 |
+-------------------+-------+
4 rows in set (0.00 sec)
SHOW PROCESSLIST;
输出示例:
SHOW PROCESSLIST;
+----+-----------------+---------------------+------+---------+------+------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-----------------+---------------------+------+---------+------+------------------------+------------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 80 | Waiting on empty queue | NULL |
| 8 | root | 192.168.144.1:44052 | NULL | Query | 0 | init | SHOW PROCESSLIST |
+----+-----------------+---------------------+------+---------+------+------------------------+------------------+
2 rows in set (0.00 sec)
说明:因无其它连接,所以实际连接数为1,执行的命令为SHOW PROCESSLIST
。
max_used_connections / max_connections * 100%(理想值≈ 85%)
如果max_used_connections跟max_connections相同,那么就是max_connections设置过低或者超过服务器负载上限了,低于10%则设置过大。
本节使用golang编写程序进行连接测试,再查询服务端的连接信息。
数据库相关封闭代码如下:
package main
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"errors"
"os"
// 导入mysql驱动
_ "github.com/denisenkom/go-mssqldb"
_ "github.com/go-sql-driver/mysql"
// _ "github.com/mattn/go-oci8"
_ "github.com/mattn/go-sqlite3"
)
const (
SQL_TIMEOUT_S = 10 // 连接超时时间 10 秒
)
// 根据传入参数解析
func CreateSQLDb(dbstr string) (SQLDB *sql.DB, DBType string) {
fmt.Println("connecting db...")
var err error
if len(dbstr) == 0 {
fmt.Println("sql string is empty")
os.Exit(0)
}
if strings.Contains(dbstr, "encrypt=disable") == true {
SQLDB, err = CreateSqlServer(dbstr)
DBType = "sqlserver"
} else if strings.Contains(dbstr, "@tcp") == true {
SQLDB, err = CreateMysql(dbstr)
DBType = "mysql"
} else if strings.Contains(strings.ToUpper(dbstr), "DB3") == true {
SQLDB, err = CreateSqlite3(dbstr)
DBType = "sqlite"
} else {
fmt.Printf("dbstr %v not support\n", dbstr)
os.Exit(0)
}
/*
else if strings.Contains(dbstr, "latelee_pdb") == true ||
strings.Contains(dbstr, "ORCLCDB") == true {
SQLDB, err = db.CreateOracle(dbstr)
DBType = "oracle"
}
*/
if err != nil {
fmt.Println("connect db error: ", err)
}
return
}
/
func IsExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
func CreateSqlServer(dbstr string) (sqldb *sql.DB, err error) {
connCh := make(chan int)
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*SQL_TIMEOUT_S)
defer cancel()
go func() {
sqldb, err = sql.Open("mssql", dbstr)
if err != nil {
err = errors.New("open database failed: " + err.Error())
connCh <- -1
return
}
// Open不一定会连接数据库,Ping可能会连接
err = sqldb.Ping()
if err != nil {
err = errors.New("connect database failed: " + err.Error())
connCh <- -1
return
}
connCh <- 0
}()
select {
case <-timeoutCtx.Done():
sqldb = nil
err = errors.New("connect to sqlserver timeout")
case ret := <-connCh:
if ret == 0 {
fmt.Println("connect to sqlserver ok")
err = nil
}
}
return
}
func CreateMysql(dbstr string) (sqldb *sql.DB, err error) {
connCh := make(chan int)
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*SQL_TIMEOUT_S)
defer cancel()
go func() {
sqldb, err = sql.Open("mysql", dbstr)
if err != nil {
err = errors.New("open database failed: " + err.Error())
connCh <- -1
return
}
err = sqldb.Ping()
if err != nil {
err = errors.New("connect database failed: " + err.Error())
connCh <- -1
return
}
connCh <- 0
}()
select {
case <-timeoutCtx.Done():
sqldb = nil
err = errors.New("connect to mysql timeout")
case ret := <-connCh:
if ret == 0 {
fmt.Println("connect to mysql ok")
err = nil
}
}
return
}
func CreateSqlite3(dbname string) (sqldb *sql.DB, err error) {
if !IsExist(dbname) {
return nil, errors.New("open database failed: " + dbname + " not found")
}
sqldb, err = sql.Open("sqlite3", dbname)
if err != nil {
return nil, errors.New("open database failed: " + err.Error())
}
err = sqldb.Ping()
if err != nil {
return nil, errors.New("connect database failed: " + err.Error())
}
fmt.Println("connect to ", dbname, "ok")
return
}
// note:暂时不使用oracle
func CreateOracle(dbstr string) (sqldb *sql.DB, err error) {
// connCh := make(chan int)
// timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*SQL_TIMEOUT_S)
// defer cancel()
// go func() {
// sqldb, err = sql.Open("oci8", dbstr)
// if err != nil {
// err = errors.New("open database failed: " + err.Error())
// connCh <- -1
// return
// }
// err = sqldb.Ping()
// if err != nil {
// err = errors.New("connect database failed: " + err.Error())
// connCh <- -1
// return
// }
// connCh <- 0
// }()
// // log.Println("connect to ", dbParam.server, dbParam.database, "ok")
// select {
// case <-timeoutCtx.Done():
// sqldb = nil
// err = errors.New("connect to oracle timeout")
// case ret := <-connCh:
// if ret == 0 {
// fmt.Println("connect to oracle ok")
// err = nil
// }
// }
return
}
主程序如下:
package main
import (
"database/sql"
"fmt"
"os"
)
var SQLDB *sql.DB // 默认以此为准
var DBType string
var ConfDBServer string = "root:123456@tcp(10.0.153.12:43306)/test?charset=utf8&interpolateParams=true&parseTime=true&loc=Local"
func initDB(dbfile string) {
SQLDB, DBType = CreateSQLDb(ConfDBServer)
if SQLDB == nil {
os.Exit(0)
}
}
var user_table_sql string = `CREATE TABLE user (
id bigint(20) NOT NULL,
email varchar(128) DEFAULT NULL,
first_name varchar(128) DEFAULT NULL,
last_name varchar(128) DEFAULT NULL,
username varchar(128) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`
var user_table_sql_1 string = `CREATE TABLE user (
id bigint(20) NOT NULL,
email varchar(128) DEFAULT NULL,
first_name varchar(128) DEFAULT NULL,
last_name varchar(128) DEFAULT NULL,
username varchar(128) DEFAULT NULL,
`
var user_table_sql_3 string = `
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`
/
func createDBTable(sqldb *sql.DB) {
_, err := sqldb.Exec(user_table_sql)
if err != nil {
fmt.Printf("Exec sql failed: [%v] [%v] \n", err, user_table_sql)
}
}
func createDBTableLong(sqldb *sql.DB) {
count := 200
mysql := ""
for i := 0; i < count; i++ {
mysql += fmt.Sprintf("username%v int DEFAULT NULL,", i)
}
realsql := user_table_sql_1 + mysql + user_table_sql_3
// fmt.Println("dddd ", realsql)
// return
_, err := sqldb.Exec(realsql)
if err != nil {
fmt.Printf("Exec sql failed: [%v] [%v] \n", err, realsql)
}
for {
// 死循环查询连接数
}
}
func main() {
fmt.Println("db test...")
initDB(ConfDBServer)
createDBTableLong(SQLDB)
}
在服务库中查询当前连接数:
mysql> SHOW PROCESSLIST;
+----+-----------------+---------------------+------+---------+------+------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-----------------+---------------------+------+---------+------+------------------------+------------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 1529 | Waiting on empty queue | NULL |
| 11 | root | 192.168.208.1:49212 | test | Query | 0 | init | SHOW PROCESSLIST |
| 21 | root | 10.0.11.23:60858 | test | Sleep | 23 | | NULL |
+----+-----------------+---------------------+------+---------+------+------------------------+------------------+
3 rows in set (0.00 sec)
限于时间,本文仅是开头,后续基于测试程序按需进行各种测试。计划将其作为数据库测试工具,可以用于国产化适配数据库选项,如:客户端库友好性(如上述代码,可直接用于MySQL、TiDB的连接);数据表支持最大列数量;一列的存储大小;等等。
mysql官方配置说明:https://dev.mysql.com/doc/refman/8.1/en/server-system-variables.html