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中的atexit模块
Mar 07 Python
Python面向对象编程基础解析(一)
Oct 26 Python
python 编码规范整理
May 05 Python
Python列表生成式与生成器操作示例
Aug 01 Python
PyQt4 treewidget 选择改变颜色,并设置可编辑的方法
Jun 17 Python
Python交互式图形编程的实现
Jul 25 Python
使用Python代码实现Linux中的ls遍历目录命令的实例代码
Sep 07 Python
使用python将最新的测试报告以附件的形式发到指定邮箱
Sep 20 Python
python面向对象之类属性和类方法案例分析
Dec 30 Python
Python栈的实现方法示例【列表、单链表】
Feb 22 Python
Python日志:自定义输出字段 json格式输出方式
Apr 27 Python
Python基于mediainfo批量重命名图片文件
Dec 29 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 传值赋值与引用赋值的区别
2010/12/29 PHP
php通用防注入程序 推荐
2011/02/26 PHP
PHP中FTP相关函数小结
2016/07/15 PHP
PHP 网站修改默认访问文件的nginx配置
2017/05/27 PHP
php关联数组与索引数组及其显示方法
2018/03/12 PHP
php对微信支付回调处理的方法
2018/08/23 PHP
javascript中检测变量的类型的代码
2010/12/28 Javascript
jquery实现图片放大镜功能
2015/11/23 Javascript
详解javascript高级定时器
2015/12/31 Javascript
JavaScript链式调用实例浅析
2018/12/19 Javascript
详解js 创建对象的几种方法
2019/03/08 Javascript
vue单页应用的内存泄露定位和修复问题小结
2019/08/02 Javascript
layui table设置某一行的字体颜色方法
2019/09/05 Javascript
[02:02]DOTA2英雄基础教程 斯拉达
2013/12/11 DOTA
python选择排序算法的实现代码
2013/11/21 Python
python之import机制详解
2014/07/03 Python
python threading模块操作多线程介绍
2015/04/08 Python
浅析AST抽象语法树及Python代码实现
2016/06/06 Python
在unittest中使用 logging 模块记录测试数据的方法
2018/11/30 Python
Python3 导入上级目录中的模块实例
2019/02/16 Python
python中sort和sorted排序的实例方法
2019/08/26 Python
python2.7实现复制大量文件及文件夹资料
2019/08/31 Python
Django实现简单网页弹出警告代码
2019/11/15 Python
Python 类的魔法属性用法实例分析
2019/11/21 Python
PyTorch中 tensor.detach() 和 tensor.data 的区别详解
2020/01/06 Python
使用python客户端访问impala的操作方式
2020/03/28 Python
Python爬虫后获取重定向url的两种方法
2021/01/19 Python
惠普香港官方商店:HP香港
2019/04/30 全球购物
构造器Constructor是否可被override?
2013/08/06 面试题
安全检查管理制度
2014/02/02 职场文书
贷款委托书
2014/08/01 职场文书
2014年化工厂工作总结
2014/11/25 职场文书
2014年机关工会工作总结
2014/12/19 职场文书
转学证明范本
2015/06/19 职场文书
电子表的操作介绍说明书
2019/10/28 职场文书
Java 获取Word中所有的插入和删除修订的方法
2022/04/06 Java/Android