Python3 多线程(连接池)操作MySQL插入数据


Posted in Python onJune 09, 2021

多线程(连接池)操作MySQL插入数据

针对于此篇博客的收获心得:

  • 首先是可以构建连接数据库的连接池,这样可以多开启连接,同一时间连接不同的数据表进行查询,插入,为多线程进行操作数据库打基础
  • 多线程根据多连接的方式,需求中要完成多语言的入库操作,我们可以启用多线程对不同语言数据进行并行操作
  • 在插入过程中,一条一插入,比较浪费时间,我们可以把数据进行积累,积累到一定的条数的时候,执行一条sql命令,一次性将多条数据插入到数据库中,节省时间cur.executemany

1.主要模块

DBUtils : 允许在多线程应用和数据库之间连接的模块套件
Threading : 提供多线程功能

2.创建连接池

PooledDB 基本参数:

  • mincached : 最少的空闲连接数,如果空闲连接数小于这个数,Pool自动创建新连接;
  • maxcached : 最大的空闲连接数,如果空闲连接数大于这个数,Pool则关闭空闲连接;
  • maxconnections : 最大的连接数;
  • blocking : 当连接数达到最大的连接数时,在请求连接的时候,如果这个值是True,请求连接的程序会一直等待,直到当前连接数小于最大连接数,如果这个值是False,会报错;
def mysql_connection():
    maxconnections = 15  # 最大连接数
    pool = PooledDB(
        pymysql,
        maxconnections,
        host='localhost',
        user='root',
        port=3306,
        passwd='123456',
        db='test_DB',
        use_unicode=True)
    return pool

# 使用方式
pool = mysql_connection()
con = pool.connection()

3.数据预处理

文件格式:txt

共准备了四份虚拟数据以便测试,分别有10万, 50万, 100万, 500万行数据

MySQL表结构如下图:

Python3 多线程(连接池)操作MySQL插入数据

数据处理思路 :

  • 每一行一条记录,每个字段间用制表符 “\t” 间隔开,字段带有双引号;
  • 读取出来的数据类型是 Bytes ;
  • 最终得到嵌套列表的格式,用于多线程循环每个任务每次处理10万行数据;

格式 : [ [(A,B,C,D), (A,B,C,D),(A,B,C,D),…], [(A,B,C,D), (A,B,C,D),(A,B,C,D),…], [], … ]

import re
import time

st = time.time()
with open("10w.txt", "rb") as f:
    data = []
    for line in f:
        line = re.sub("\s", "", str(line, encoding="utf-8"))
        line = tuple(line[1:-1].split("\"\""))
        data.append(line)
    n = 100000  # 按每10万行数据为最小单位拆分成嵌套列表
    result = [data[i:i + n] for i in range(0, len(data), n)]
print("10万行数据,耗时:{}".format(round(time.time() - st, 3)))

# 10万行数据,耗时:0.374
# 50万行数据,耗时:1.848
# 100万行数据,耗时:3.725
# 500万行数据,耗时:18.493

4.线程任务

每调用一次插入函数就从连接池中取出一个链接操作,完成后关闭链接;
executemany 批量操作,减少 commit 次数,提升效率;

def mysql_insert(*args):
    con = pool.connection()
    cur = con.cursor()
    sql = "INSERT INTO test(sku,fnsku,asin,shopid) VALUES(%s, %s, %s, %s)"
    try:
        cur.executemany(sql, *args)
        con.commit()
    except Exception as e:
        con.rollback()  # 事务回滚
        print('SQL执行有误,原因:', e)
    finally:
        cur.close()
        con.close()

5.启动多线程

代码思路 :

设定最大队列数,该值必须要小于连接池的最大连接数,否则创建线程任务所需要的连接无法满足,会报错 : pymysql.err.OperationalError: (1040, ‘Too many connections')循环预处理好的列表数据,添加队列任务如果达到队列最大值 或者 当前任务是最后一个,就开始多线程队执行队列里的任务,直到队列为空;

def task():
    q = Queue(maxsize=10)  # 设定最大队列数和线程数
    # data : 预处理好的数据(嵌套列表)
    while data:
        content = data.pop()
        t = threading.Thread(target=mysql_insert, args=(content,))
        q.put(t)
        if (q.full() == True) or (len(data)) == 0:
            thread_list = []
            while q.empty() == False:
                t = q.get()
                thread_list.append(t)
                t.start()
            for t in thread_list:
                t.join()

6.完整示例

import pymysql
import threading
import re
import time
from queue import Queue
from DBUtils.PooledDB import PooledDB

class ThreadInsert(object):
    "多线程并发MySQL插入数据"
    def __init__(self):
        start_time = time.time()
        self.pool = self.mysql_connection()
        self.data = self.getData()
        self.mysql_delete()
        self.task()
        print("========= 数据插入,共耗时:{}'s =========".format(round(time.time() - start_time, 3)))
        
    def mysql_connection(self):
        maxconnections = 15  # 最大连接数
        pool = PooledDB(
            pymysql,
            maxconnections,
            host='localhost',
            user='root',
            port=3306,
            passwd='123456',
            db='test_DB',
            use_unicode=True)
        return pool

    def getData(self):
        st = time.time()
        with open("10w.txt", "rb") as f:
            data = []
            for line in f:
                line = re.sub("\s", "", str(line, encoding="utf-8"))
                line = tuple(line[1:-1].split("\"\""))
                data.append(line)
        n = 100000    # 按每10万行数据为最小单位拆分成嵌套列表
        result = [data[i:i + n] for i in range(0, len(data), n)]
        print("共获取{}组数据,每组{}个元素.==>> 耗时:{}'s".format(len(result), n, round(time.time() - st, 3)))
        return result

    def mysql_delete(self):
        st = time.time()
        con = self.pool.connection()
        cur = con.cursor()
        sql = "TRUNCATE TABLE test"
        cur.execute(sql)
        con.commit()
        cur.close()
        con.close()
        print("清空原数据.==>> 耗时:{}'s".format(round(time.time() - st, 3)))

    def mysql_insert(self, *args):
        con = self.pool.connection()
        cur = con.cursor()
        sql = "INSERT INTO test(sku, fnsku, asin, shopid) VALUES(%s, %s, %s, %s)"
        try:
            cur.executemany(sql, *args)
            con.commit()
        except Exception as e:
            con.rollback()  # 事务回滚
            print('SQL执行有误,原因:', e)
        finally:
            cur.close()
            con.close()

    def task(self):
        q = Queue(maxsize=10)  # 设定最大队列数和线程数
        st = time.time()
        while self.data:
            content = self.data.pop()
            t = threading.Thread(target=self.mysql_insert, args=(content,))
            q.put(t)
            if (q.full() == True) or (len(self.data)) == 0:
                thread_list = []
                while q.empty() == False:
                    t = q.get()
                    thread_list.append(t)
                    t.start()
                for t in thread_list:
                    t.join()
        print("数据插入完成.==>> 耗时:{}'s".format(round(time.time() - st, 3)))

if __name__ == '__main__':
    ThreadInsert()

插入数据对比

共获取1组数据,每组100000个元素.== >> 耗时:0.374's
清空原数据.== >> 耗时:0.031's
数据插入完成.== >> 耗时:2.499's
=============== 10w数据插入,共耗时:3.092's ===============
共获取5组数据,每组100000个元素.== >> 耗时:1.745's
清空原数据.== >> 耗时:0.0's
数据插入完成.== >> 耗时:16.129's
=============== 50w数据插入,共耗时:17.969's ===============
共获取10组数据,每组100000个元素.== >> 耗时:3.858's
清空原数据.== >> 耗时:0.028's
数据插入完成.== >> 耗时:41.269's
=============== 100w数据插入,共耗时:45.257's ===============
共获取50组数据,每组100000个元素.== >> 耗时:19.478's
清空原数据.== >> 耗时:0.016's
数据插入完成.== >> 耗时:317.346's
=============== 500w数据插入,共耗时:337.053's ===============

7.思考/总结

思考 :多线程+队列的方式基本能满足日常的工作需要,但是细想还是有不足;
例子中每次执行10个线程任务,在这10个任务执行完后才能重新添加队列任务,这样会造成队列空闲.如剩余1个任务未完成,当中空闲数 9,当中的资源时间都浪费了;
是否能一直保持队列饱满的状态,每完成一个任务就重新填充一个.

 到此这篇关于Python3 多线程(连接池)操作MySQL插入数据的文章就介绍到这了,更多相关Python3 多线程插入MySQL数据内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python中的闭包总结
Sep 18 Python
Python 实现一个颜色色值转换的小工具
Dec 06 Python
关于Django显示时间你应该知道的一些问题
Dec 25 Python
python中退出多层循环的方法
Nov 27 Python
详解python配置虚拟环境
Apr 08 Python
pyqt5 删除layout中的所有widget方法
Jun 25 Python
python如何给字典的键对应的值为字典项的字典赋值
Jul 05 Python
python面试题之列表声明实例分析
Jul 08 Python
Django框架表单操作实例分析
Nov 04 Python
Python远程开发环境部署与调试过程图解
Dec 09 Python
基于python连接oracle导并出数据文件
Apr 28 Python
如何使用Python处理HDF格式数据及可视化问题
Jun 24 Python
jupyter notebook保存文件默认路径更改方法汇总(亲测可以)
Django rest framework如何自定义用户表
Jun 09 #Python
如何使用Python提取Chrome浏览器保存的密码
Jun 09 #Python
python缺失值的解决方法总结
Jun 09 #Python
Python提取PDF指定内容并生成新文件
Python激活Anaconda环境变量的详细步骤
Jun 08 #Python
Python序列化与反序列化相关知识总结
Jun 08 #Python
You might like
PHP实现的mysql主从数据库状态检测功能示例
2017/07/20 PHP
PHPUnit测试私有属性和方法功能示例
2018/06/12 PHP
Laravel Eloquent分表方法并使用模型关联的实现
2019/11/25 PHP
jquery 学习笔记一
2010/04/07 Javascript
Ext 今日学习总结
2010/09/19 Javascript
jquery仿QQ商城带左右按钮控制焦点图片切换滚动效果
2013/06/27 Javascript
省市区三级联动下拉框菜单javascript版
2015/08/11 Javascript
Node.js制作简单聊天室
2017/01/12 Javascript
JavaScript实现动态增删表格的方法
2017/03/09 Javascript
JavaScript中splice与slice的区别
2017/05/09 Javascript
JS SetInterval 代码实现页面轮询
2017/08/11 Javascript
10个最优秀的Node.js MVC框架
2017/08/24 Javascript
JavaScript基础之流程控制语句的用法
2017/08/31 Javascript
使用Fullpage插件快速开发整屏翻页的页面
2017/09/13 Javascript
angularjs实现猜大小功能
2017/10/23 Javascript
Vue项目中使用better-scroll实现一个轮播图自动播放功能
2018/12/03 Javascript
vue自定义组件实现双向绑定
2021/01/13 Vue.js
python实现JAVA源代码从ANSI到UTF-8的批量转换方法
2015/08/10 Python
python中装饰器级连的使用方法示例
2017/09/29 Python
Tornado 多进程实现分析详解
2018/01/12 Python
Tensorflow之Saver的用法详解
2018/04/23 Python
Python实现自定义函数的5种常见形式分析
2018/06/16 Python
PyCharm代码回滚,恢复历史版本的解决方法
2018/10/22 Python
python用fsolve、leastsq对非线性方程组求解
2018/12/15 Python
python实现五子棋小游戏
2020/03/25 Python
Python 给屏幕打印信息加上颜色的实现方法
2019/04/24 Python
Python动态参数/命名空间/函数嵌套/global和nonlocal
2019/05/29 Python
20行Python代码实现一款永久免费PDF编辑工具的实现
2020/08/27 Python
CSS3制作ajax loader icon实现思路及代码
2013/08/25 HTML / CSS
从一次项目重构说起CSS3自定义变量在项目的使用方法
2021/03/01 HTML / CSS
萨克斯第五大道的折扣店:Saks Fifth Avenue OFF 5TH
2016/08/25 全球购物
银行毕业实习自我鉴定
2013/09/19 职场文书
农民致富事迹材料
2014/01/23 职场文书
合作意向书模板
2014/03/31 职场文书
会计求职简历自我评价
2015/03/10 职场文书
javascript函数式编程基础
2021/09/15 Javascript