使用连接池方式和多线程方式连接mysql的测试说明

2023-05-16

  前面文章讨论了mysql做高可用的配置,参考文章链接,而本文则是开发项目过程需要用的部分,从配置数据库到实用数据库,以及再用SQL做BI分析再到SQL优化,这些都是全栈工程师的基本功。

1、连接池测试mysql默认连接配置

  先出简单的测试连接池或多线程并发的脚本,这里先借用DBUtils创建连接池,文章后面会给出无须借用第三方库也可以实现实用的连接池。

import time
import pymysql
import threading
from DBUtils.PooledDB import PooledDB

db_info={
            'host':'192.168.100.5',
            'port':34312, # 改掉默认端口号,安全考虑
            'user':'***',
            'password':'***',
            'db':'erp_app',
            'charset':'utf8'
        }


db_pool = PooledDB(
    creator=pymysql,
    maxconnections=3000,  # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=0,  # 初始化时,连接池中至少创建的空闲的链接,0表示不创建
    maxcached=0,  # 连接池中最多闲置的连接,0和None不限制
    maxshared=0, # 连接池中最多共享的连接数量,0和None表示全部共享。
    blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True
    maxusage=None,  # 一个连接最多被重复使用的次数,None表示无限制
    **db_info # 数据库账户等信息
)

# 测试插入数据
insert_sql = ("insert into apps_name "
              "(id,app_log_name,log_path,log_date) "
              "values(null,%(app_log_name)s,%(log_path)s,null)")
insert_data={
    'app_log_name':'BI-Access-Log',
    'log_path':'/opt/data/apps_log/'
    }


def save_data(mode,inst_sql,inst_data):
    '''
    m:多线程模式,c:连接池模式
    '''
    if mode == 'm':
        conn=pymysql.connect(**db_info)
    else:
        conn = db_pool.connection()
    try:
        with conn.cursor() as cur:
            resl=cur.execute(inst_sql,inst_data)
            conn.commit()
    except pymysql.MySQLError as err:
        conn.rollback()
    finally:
        # PooledDB连接池关闭方法其实不是真的把该连接关闭,而是将该连接由放入池的队列里,在后文会看到该逻辑的实现
        conn.close()


def multi_insert(mode,nums=151):
    treads=[]
    for i in range(nums):
        t=threading.Thread(target=save_data,args=(mode,insert_sql,insert_data))
        treads.append(t)
    for t in treads:
        t.start()
    for t in treads:
        t.join()

def run(mode,request_nums):
    start=time.time()
    multi_insert(mode,request_nums)
    end=time.time()
    cost=end-start
    print('cost:{0:.3} s'.format(cost))


if __name__=='__main__':
    run('c',100000)

查看mariadb默认设置的最大连接数为151

MariaDB [erp_app]> show variables like '%max_con%';
+---------------------------------------+-------+
| Variable_name                         | Value |
+---------------------------------------+-------+
| extra_max_connections                 | 1     |
| max_connect_errors                    | 100   |
| max_connections                       | 151   |
| performance_schema_max_cond_classes   | 80    |
| performance_schema_max_cond_instances | -1    |
+---------------------------------------+-------+

  运行测试脚本,注意这里是连接池10万个连接的并发,对于server端的mysql来说,也就是同时有200个并发connections,就已经出错了,这就是模拟了客户端多线程大量并发消耗完mysql 最大连接资源引起error

pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '192.168.100.5' ([Errno 24] Too many open files)")
2、高并发连接数据库出错分析与测试

  出现以上情况,可通过设置mysql max_connections最大值,来保证并发量,测试mysql在有限物理资源条件下可达到的最大连接数,随便设一个大值例如10000000,最后可mysql单机可设定的最大连接数为10万

MariaDB [erp_app]> set GLOBAL max_connections=10000000;
Query OK, 0 rows affected, 1 warning (0.000 sec)

MariaDB [erp_app]> show variables like '%max_con%';
+---------------------------------------+--------+
| Variable_name                         | Value  |
+---------------------------------------+--------+
| extra_max_connections                 | 1      |
| max_connect_errors                    | 100    |
| max_connections                       | 100000 |
| performance_schema_max_cond_classes   | 80     |
| performance_schema_max_cond_instances | -1     |
+---------------------------------------+--------+
5 rows in set (0.001 sec)

  在这里,使用多线程方式,测试脚本发起10万个线程并发请求,运行后程序很快出现socket请求打满客户端系统缓冲区,导致系统级别出错,如下

pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '192.168.
100.5' ([WinError 10055] 由于系统缓冲区空间不足或队列已满,不能执行套接字上的操
作。)")

再查看服务器响应的最大连接数,成功连接仅有745个

MariaDB [erp_app]> show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 745   |
+----------------------+-------+
1 row in set (0.001 sec)
3、为何选用连接池优化连接?

  从第二部分的测试可知,因为每个线程创建单独的连接,当并发量大时,会造成client和msyql server之间的频繁“线程创建tcp连接-登录-线程退出关闭tcp连接”,
若采用连接池方式连接mysql,则是重用数据库服务端的tcp通道,以达到client和MySQL之间只需维持较少的连接,单个客户端可以提高其并发量,且消耗较低的物理资源。

打个不一定恰当的通俗比喻:

有10000辆车同时要从A点到达B点,出发前,A、B之间没有路,需要先搭建

1)多线程方式:需要1000个路面施工队同时搭建完1000条“高速路”后,才能同时出发,可见需要消耗非常多资源(10000个施工队以及10000条高速路资源),等10000辆车到达B点后,10000个施工队又得去拆除10000条高速路,非常耗资源

2)连接池方式(假设连接池大小为1000):需要1000个路面施工队同时搭建完1000条“高速路”后,前面1000辆车到达B点,后面9000辆车出发时,施工队不需要再新建高速路,继续重用前面搭建的1000条“高速路”,极大降低的物理资源浪费。

连接池重要两个逻辑:

  • 在程序创建连接的时候,可以从连接池队列中取出一个空闲的连接,不需要重新初始化连接,提升获取连接的速度

  • 关闭连接的时候,把连接放回存放连接池的队列中,而不是真正的关闭,所以可以减少频繁地打开和关闭tcp通道

4、继续测试两种连接效果

  mysql的默认最大连接数已设置为1万个
1)开启2000个线程,重复1次,使用多线程并发,不出意外,mysql接收到已用连接数为1022个,之后的请求连接全部error 中断

(py36) [root@localhost opt]# python insert_test.py
cost:1.99 s

pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '192.168.100.4' ([Errno 24] Too many open files)")

MariaDB [(none)]> show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 1022  |
+----------------------+-------+
1 row in set (0.000 sec)
  1. 开启多线程6000个并发,运行出错,6000个线程直接把客户端的系统缓冲区打满,无法继续运行
pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '192.168.100.4' ([WinError 10055] 由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。)")

3)连接池开启6000个并发,连接池最大连接限制3000个,运行没有问题,cost:24.3 s

在mysql也可以看到最大已用连接数为3000个

MariaDB [(none)]> show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 3000  |
+----------------------+-------+
1 row in set (0.001 sec)

5 、自行实现简易使用的mysql连接池

import threading
from queue import Queue
import pymysql
from pymysql.cursors import DictCursor

db_info={
            'host':'192.168.100.4',
            'port':3306,
            'user':'***',
            'password':'****',
            'db':'erp_app',
            'charset':'utf8'
        }


class ConnPoolException(Exception):
    """连接池出错 """


class MariaDBPool(object):
    _inst_lock=threading.RLock()
    
    def __init__(self, connections, **db_conf):
        self.__connections = connections
        self.__pool = Queue(connections)
        # 在init阶段,就已经创建好指定的连接,全部put到共享队列
        for i in range(self.__connections):
            try:
                conn = pymysql.connect(**db_conf)
                self.__pool.put(conn)
            except ConnPoolException as e:
                raise IOError

	# 单例模式创建连接池,个人喜欢用__new__方法创建,简洁,且使用了递归锁,保证在多线程方式创建单例模式的对象都是同一对象
    def __new__(cls, *args, **kwargs):
        with cls._inst_lock:
            if not hasattr(cls,'_inst'):
                cls._inst=object.__new__(cls)
        return cls._inst

    def execute_insert(self,sql,data_dict=None):
        conn = self.__pool.get()
        cursor = conn.cursor(DictCursor)
        try:
            result=cursor.execute(sql,data_dict) if data_dict else cursor.execute(sql)
            conn.commit()
        except ConnPoolException as e:
           # 这里就是重点,只是关闭了游标,连接对像又返回池里   
            conn.rollback()
            cursor.close()
            self.__pool.put(conn)
            return False
        else:
            # 这里就是重点,只是关闭了游标,连接对像又返回池里
            cursor.close()
            self.__pool.put(conn)
            return result

    def executemany_insert(self,sql,data_dict_list=None):
        conn = self.__pool.get()
        cursor = conn.cursor(DictCursor)
        try:
            result=cursor.execute(sql,data_dict) if data_dict_list else cursor.executemany(sql)
        except ConnPoolException as e:
            conn.rollback()
            cursor.close()
            self.__pool.put(conn)
            return False
        else:
            cursor.close()
            self.__pool.put(conn)
            return result
            
    # 这里才是真正的关闭所有连接池
    def close(self):
        for i in range(self.__connections):
            self.__pool.get().close()

  就本文测试的数据库以及简单表结构而言,使用该连接池模块,注意测试之前,需重启mysql,保证数据库最大已连接数为1,也即清空历史残留连接,以便做新测试对比。12000个并发,mysql设定最大可用连接数:3000,单例模式,cost:22.2 s,程序不会出现任何异常,当然因表结构和服务器性能情况而不同。

  这里顺便提下oracle线程池,部分项目使用oracle数据库,cx_Oracle也支持使用连接池方式连接,大致流程如下,也可根据使用习惯封装更适合自身业务需要的模块。

import cx_Oracle
db_user='foo'
db_pwd='barbar'
db_host='192.168.100.7'
db_name='erp_app'
dsn = cx_Oracle.makedsn(db_host, "1521", db_name)
db_config = {
    'user': db_user,
    'password': db_pwd,
    'dsn': dsn,
    'min': 1,
    'max': 1000,
    'increment': 1,
    'threaded': True
}
orc_pool = cx_Oracle.SessionPool(**dbConfig)

  若使用多线程方式,尤其数量大的情况下(500以上),很容易把底层bug爆出来,cx_Oracle会引发python解释器崩溃。其实建议,只要是连接数据库,所引入的第三方库支持线程池方法的话,都建议用线程池,哪怕你的插入数据不是太频繁或并发量不大,减少程序自身出错,也降低数据库连接压力。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用连接池方式和多线程方式连接mysql的测试说明 的相关文章

随机推荐

  • 【转载+修改】Gnome菜单项与文件打开方式(文件关联)的更改

    转载自 xff1a http hi baidu com red woods blog item 30a5f845a2247f24cffca397 html 转载有修改 xff01 在KDE中我们可以使用系统设置中提供的设置进行文件关联的修改
  • Kali Linux从零基础入门到精通,看完这一篇就够了。

    1 目录 基于Android设备的Kali Linux渗透测试教程基于Android设备的Kali Linux渗透测试教程2Web渗透测试使用kali linuxkali linux中文指南kali linux wireless pente
  • 客户机无法通过mstsc连接到远程主机的解决方法

    客户机无法通过 mstsc 连接到远程主机的解决方法 症状 当通过 mstsc 命令进行连接时 系统提示 客户端无法连接远程计算机 xff1b 连接可能没有启用 xff0c 或者计算机太忙 xff0c 无法接受新连接 也可能网络问题使您无法
  • 五个同步问题的经典模型之一:生产者/消费者问题

    也叫缓存绑定问题 xff08 bounded buffer xff09 xff0c 是一个经典的 多进程同步问题 单生产者和单消费者 有两个进程 xff1a 一组生产者进程和一组消费者进程共享一个初始为空 固定大小为n的缓存 xff08 缓
  • Android 以太网/有线网Ethernet功能开发

    1 功能介绍 以太网的功能是允许设备提供硬件接口通过插入网线的形式访问互联网的功能 接入网线之后 xff0c 设备可以动态的获取 IP xff0c DNS xff0c Gateway等一系列网络属性 xff0c 我们也可以手动配置设备的网络
  • 解决www.54kk.com/baidu劫持浏览器的问题

    endurer 原创 2005 10 27第一版 endurer注 xff1a 为了安全起见 xff0c 下文中的 http 均用 hxxp 代替 刚才一位同事的电脑中的浏览器被恶意网站劫持了 xff0c 请我帮忙处理 同事的电脑使用的是
  • Automatic Login

    sudo vim etc gdm custom confAdd the following lines to the field daemon AutomaticLoginEnable 61 true AutomaticLogin 61 i
  • Java Annotation手册

    版权声明 xff1a 本文可以自由转载 xff0c 转载时请务必以超链接形式标明文章原始出处和作者信息及本声明 作者 cleverpig 作者的Blog http blog matrix org cn page cleverpig 原文 h
  • shell 函数 入参说明

    1 入参个数 2 入参 0 脚本名 1第一个参数 3 64 和 xff1a 34 64 34 34 34 都是所有入参 64 将入参变成一个数组 将入参变成一个字符串 4 数组作为入参 fucn2 arr xff0c 函数内部获取入参数组
  • 程序媛工作几年后的感受!体验?

    黑客技术 点击右侧关注 xff0c 了解黑客的世界 xff01 Java开发进阶 点击右侧关注 xff0c 掌握进阶之路 xff01 Python开发 点击右侧关注 xff0c 探讨技术话题 xff01 作者 xff1a hq nuan 来
  • TIOBE 5月编程语言榜单出炉,C#最受开发者欢迎,C++将冲击Top 3

    x1f447 x1f447 关注后回复 进群 xff0c 拉你进程序员交流群 x1f447 x1f447 TIOBE Index for May 2022 和 4 月相比 xff0c 本月编程语言 Top 10 并没有明显的位置变化 xff
  • crontab 定时任务避免重复执行

    使用crontab设置一个脚本每个一段时间自动执行一次 xff0c 当脚本的执行时间超过crontab设置的时间间隔 xff0c 那个脚本就会在同一时刻同时执行 比如设置crontab每隔五分钟执行一次task sh xff1a span
  • ubuntu 通过 apt-get 安装软件失败时的解决方案

    最近在 vmware上的ubuntu系统下安装 软件时出现安装失败情况 xff0c 在网上搜了一通 xff0c 终于找到了解决方案 遇到的问题和解决方案如下 xff1a 一 apt get install vim二 apt get upda
  • Spring注解处理机制

    前言 众所周知 xff0c spring 从 2 5 版本以后开始支持使用注解代替繁琐的 xml 配置 xff0c 到了 springboot 更是全面拥抱了注解式配置 平时在使用的时候 xff0c 点开一些常见的等注解 xff0c 会发现
  • 解决SpringBoot使用时类找不到问题

    解决方案 第一步 xff1a 勾选这个选项 第二步 xff0c 在pom xml中添加以下代码 lt resources gt lt resource gt lt directory gt src main resources lt dir
  • java设计模式之建造者模式(Builder Pattern)

    目的 xff1a 将产品与产品的创建过程解耦 他是按照相应的步骤来构建产品 下面看一下UML序列图 对于序列图的一个解释 下面来上一个标准代码 Product java package com pxx public class Produc
  • 如何在 Github Pages 搭建库(创建免费域名)来管理和浏览自己的项目

    看了 这篇文章 你能学会 两大技能 如何在 Github Pages 上搭建库来管理自己的项目你能访问你的项目 就像访问域名一样 查看自己做的网页 说明 像我们学前端的朋友 xff0c 好不容易做好一个很炫的网页 xff0c 没法放在网站上
  • vim 快捷键修改

    ubuntu默认的vim确实不好用 xff0c 但它最强大的地方在于可修改的配置文件 xff0c 以及专门为vim所开发的vimscript脚本语言 后者暂时不用学习 xff0c 先来研究一下配置文件 vimrc 是控制 vim 行为的配置
  • Excel合并计算和分类汇总

    一 实现合并计算 合并计算主要实现将几个分开的表格按照需求的函数功能计算到一个表中 xff1a 1 分类合并 将下面三个城市的销售额分类合并到一个表当中 xff08 这里的销售额必须指明地区 xff0c 不然合并计算时会统计求和 xff09
  • 使用连接池方式和多线程方式连接mysql的测试说明

    前面文章讨论了mysql做高可用的配置 xff0c 参考文章链接 xff0c 而本文则是开发项目过程需要用的部分 xff0c 从配置数据库到实用数据库 xff0c 以及再用SQL做BI分析再到SQL优化 xff0c 这些都是全栈工程师的基本