杂项之pymysql连接池

本节内容

  1. 本文的诞生
  2. 连接池及单例模式
  3. 多线程提升
  4. 协程提升
  5. 后记

1.本文的诞生

由于前几天接触了pymysql,在测试数据过程中,使用普通的pymysql插入100W条数据,消耗时间很漫长,实测990s也就是16.5分钟左右才能插完,于是,脑海中诞生了一个想法,能不能造出一个连接池出来,提升数据呢?就像一根管道太小,那就多加几根管道看效果如何呢?于是。。。前前后后折腾了将近一天时间,就有了本文的诞生。。。

2.连接池及单例模式

先说单例模式吧,为什么要在这使用单例模式呢?使用单例模式能够节省资源。

其实单例模式没有什么神秘的,简单的单例模式实现其实就是在类里面定义一个变量,再定义一个类方法,这个类方法用来为调用者提供这个类的实例化对象。(ps:个人对单例模式的一点浅薄理解...)

那么连接池是怎么回事呢?原来使用pymysql创建一个conn对象的时候,就已经和mysql之间创建了一个tcp的长连接,只要不调用这个对象的close方法,这个长连接就不会断开,这样,我们创建了一组conn对象,并将这些conn对象放到队列里面去,这个队列现在就是一个连接池了。

现在,我们先用一个连接,往数据库中插入100W条数据,下面是源码:

  1. import pymysql
  2. import time
  3. start=time.time()
  4. conn = pymysql.connect(host="192.168.10.103",port=3306,user="root",passwd="",db="sql_example",charset="utf8")
  5. conn.autocommit(True) # 设置自动commit
  6. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 设置返回的结果集用字典来表示,默认是元祖
  7. data=(("男",i,"张小凡%s" %i) for i in range(1000000)) # 伪造数据,data是个生成器
  8. cursor.executemany("insert into tb1(gender,class_id,sname) values(%s,%s,%s)",data) # 可以使用executemany执行多条sql
  9. # conn.commit()
  10. cursor.close()
  11. conn.close()
  12. print("totol time:",time.time()-start)

执行结果为:

totol time: 978.7649309635162

3.多线程提升

使用多线程,在启动时创建一组线程,每个线程去连接池里面获取一个连接,然后插入数据,这样将会大大提升执行sql的速度,下面是使用多线程实现的连接池源码:

  1. from gevent import monkey
  2. monkey.patch_all()
  3.  
  4. import threading
  5.  
  6. import pymysql
  7. from queue import Queue
  8. import time
  9.  
  10. class Exec_db:
  11.  
  12. __v=None
  13.  
  14. def __init__(self,host=None,port=None,user=None,passwd=None,db=None,charset=None,maxconn=5):
  15. self.host,self.port,self.user,self.passwd,self.db,self.charset=host,port,user,passwd,db,charset
  16. self.maxconn=maxconn
  17. self.pool=Queue(maxconn)
  18. for i in range(maxconn):
  19. try:
  20. conn=pymysql.connect(host=self.host,port=self.port,user=self.user,passwd=self.passwd,db=self.db,charset=self.charset)
  21. conn.autocommit(True)
  22. # self.cursor=self.conn.cursor(cursor=pymysql.cursors.DictCursor)
  23. self.pool.put(conn)
  24. except Exception as e:
  25. raise IOError(e)
  26.  
  27. @classmethod
  28. def get_instance(cls,*args,**kwargs):
  29. if cls.__v:
  30. return cls.__v
  31. else:
  32. cls.__v=Exec_db(*args,**kwargs)
  33. return cls.__v
  34.  
  35. def exec_sql(self,sql,operation=None):
  36. """
  37. 执行无返回结果集的sql,主要有insert update delete
  38. """
  39. try:
  40. conn=self.pool.get()
  41. cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)
  42. response=cursor.execute(sql,operation) if operation else cursor.execute(sql)
  43. except Exception as e:
  44. print(e)
  45. cursor.close()
  46. self.pool.put(conn)
  47. return None
  48. else:
  49. cursor.close()
  50. self.pool.put(conn)
  51. return response
  52.  
  53. def exec_sql_feach(self,sql,operation=None):
  54. """
  55. 执行有返回结果集的sql,主要是select
  56. """
  57. try:
  58. conn=self.pool.get()
  59. cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)
  60. response=cursor.execute(sql,operation) if operation else cursor.execute(sql)
  61. except Exception as e:
  62. print(e)
  63. cursor.close()
  64. self.pool.put(conn)
  65. return None,None
  66. else:
  67. data=cursor.fetchall()
  68. cursor.close()
  69. self.pool.put(conn)
  70. return response,data
  71.  
  72. def exec_sql_many(self,sql,operation=None):
  73. """
  74. 执行多个sql,主要是insert into 多条数据的时候
  75. """
  76. try:
  77. conn=self.pool.get()
  78. cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)
  79. response=cursor.executemany(sql,operation) if operation else cursor.executemany(sql)
  80. except Exception as e:
  81. print(e)
  82. cursor.close()
  83. self.pool.put(conn)
  84. else:
  85. cursor.close()
  86. self.pool.put(conn)
  87. return response
  88.  
  89. def close_conn(self):
  90. for i in range(self.maxconn):
  91. self.pool.get().close()
  92.  
  93. obj=Exec_db.get_instance(host="192.168.10.103",port=3306,user="root",passwd="",db="sql_example",charset="utf8",maxconn=10)
  94.  
  95. def test_func(num):
  96. data=(("男",i,"张小凡%s" %i) for i in range(num))
  97. sql="insert into tb1(gender,class_id,sname) values(%s,%s,%s)"
  98. print(obj.exec_sql_many(sql,data))
  99.  
  100. job_list=[]
  101. for i in range(10):
  102. t=threading.Thread(target=test_func,args=(100000,))
  103. t.start()
  104. job_list.append(t)
  105. for j in job_list:
  106. j.join()
  107. obj.close_conn()
  108. print("totol time:",time.time()-start)

显示代码

开启10个连接池插入100W数据的时间:

totol time: 242.81142950057983

开启50个连接池插入100W数据的时间:

totol time: 192.49499201774597

开启100个线程池插入100W数据的时间:

totol time: 191.73923873901367

4.协程提升

使用协程的话,在I/O阻塞时,将会切换到其他任务去执行,这样理论上来说消耗的资源应该会比多线程要少。下面是协程实现的连接池源代码:

  1. from gevent import monkey
  2. monkey.patch_all()
  3. import gevent
  4.  
  5. import pymysql
  6. from queue import Queue
  7. import time
  8.  
  9. class Exec_db:
  10.  
  11. __v=None
  12.  
  13. def __init__(self,host=None,port=None,user=None,passwd=None,db=None,charset=None,maxconn=5):
  14. self.host,self.port,self.user,self.passwd,self.db,self.charset=host,port,user,passwd,db,charset
  15. self.maxconn=maxconn
  16. self.pool=Queue(maxconn)
  17. for i in range(maxconn):
  18. try:
  19. conn=pymysql.connect(host=self.host,port=self.port,user=self.user,passwd=self.passwd,db=self.db,charset=self.charset)
  20. conn.autocommit(True)
  21. # self.cursor=self.conn.cursor(cursor=pymysql.cursors.DictCursor)
  22. self.pool.put(conn)
  23. except Exception as e:
  24. raise IOError(e)
  25.  
  26. @classmethod
  27. def get_instance(cls,*args,**kwargs):
  28. if cls.__v:
  29. return cls.__v
  30. else:
  31. cls.__v=Exec_db(*args,**kwargs)
  32. return cls.__v
  33.  
  34. def exec_sql(self,sql,operation=None):
  35. """
  36. 执行无返回结果集的sql,主要有insert update delete
  37. """
  38. try:
  39. conn=self.pool.get()
  40. cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)
  41. response=cursor.execute(sql,operation) if operation else cursor.execute(sql)
  42. except Exception as e:
  43. print(e)
  44. cursor.close()
  45. self.pool.put(conn)
  46. return None
  47. else:
  48. cursor.close()
  49. self.pool.put(conn)
  50. return response
  51.  
  52. def exec_sql_feach(self,sql,operation=None):
  53. """
  54. 执行有返回结果集的sql,主要是select
  55. """
  56. try:
  57. conn=self.pool.get()
  58. cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)
  59. response=cursor.execute(sql,operation) if operation else cursor.execute(sql)
  60. except Exception as e:
  61. print(e)
  62. cursor.close()
  63. self.pool.put(conn)
  64. return None,None
  65. else:
  66. data=cursor.fetchall()
  67. cursor.close()
  68. self.pool.put(conn)
  69. return response,data
  70.  
  71. def exec_sql_many(self,sql,operation=None):
  72. """
  73. 执行多个sql,主要是insert into 多条数据的时候
  74. """
  75. try:
  76. conn=self.pool.get()
  77. cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)
  78. response=cursor.executemany(sql,operation) if operation else cursor.executemany(sql)
  79. except Exception as e:
  80. print(e)
  81. cursor.close()
  82. self.pool.put(conn)
  83. else:
  84. cursor.close()
  85. self.pool.put(conn)
  86. return response
  87.  
  88. def close_conn(self):
  89. for i in range(self.maxconn):
  90. self.pool.get().close()
  91.  
  92. obj=Exec_db.get_instance(host="192.168.10.103",port=3306,user="root",passwd="",db="sql_example",charset="utf8",maxconn=10)
  93.  
  94. def test_func(num):
  95. data=(("男",i,"张小凡%s" %i) for i in range(num))
  96. sql="insert into tb1(gender,class_id,sname) values(%s,%s,%s)"
  97. print(obj.exec_sql_many(sql,data))
  98.  
  99. start=time.time()
  100. job_list=[]
  101. for i in range(10):
  102. job_list.append(gevent.spawn(test_func,100000))
  103.  
  104. gevent.joinall(job_list)
  105.  
  106. obj.close_conn()
  107.  
  108. print("totol time:",time.time()-start)

显示代码

开启10个连接池插入100W数据的时间:

totol time: 240.16892313957214

开启50个连接池插入100W数据的时间:

totol time: 202.82087111473083

开启100个线程池插入100W数据的时间:

totol time: 196.1710569858551

5.后记

统计结果如下:

单线程一个连接使用时间:978.76s

  10个连接池 50个连接池 100个连接池
多线程版 242.81s 192.49s 191.74s
协程版 240.17s 202.82s 196.17s

通过统计结果显示,通过协程和多线程操作连接池插入相同数据,相对一个连接提升速度明显,但是在将连接池开到50以及100时,性能提升并没有想象中那么大,这时候,瓶颈已经不在网络I/O上了,而在数据库中,mysql在大量连接写入数据时,也会有锁的产生,这时候就需要优化数据库的相关设置了。

在对比中显示多线程利用线程池和协程利用线程池的性能差不多,但是多线程的开销比协程要大。

和大神讨论过,在项目开发中需要考虑到不同情况使用不同的技术,多线程适合使用在连接量较大,但每个连接处理时间很短的情况下,而协程适用于处理大量连接,但同时活跃的链接比较少,并且每个连接的时间量比较大的情况下。

在实际生产应用中,创建连接池可以按需分配,当连接不够用时,在连接池没达到上限的情况下,在连接池里面加入新的连接,在连接池比较空闲的情况下,关闭一些连接,实现这一个操作的原理是通过queue里面的超时时间来控制,当等待时间超过了超时时间时,说明连接不够用了,需要加入新的连接。

杂项之pymysql连接池的更多相关文章

  1. 第一篇:杂项之pymysql连接池

    杂项之pymysql连接池   杂项之pymysql连接池 本节内容 本文的诞生 连接池及单例模式 多线程提升 协程提升 后记 1.本文的诞生 由于前几天接触了pymysql,在测试数据过程中,使用普 ...

  2. pymysql 连接池

    pymysql连接池 import pymysql from DBUtils.PooledDB import PooledDB, SharedDBConnection ''' 连接池 ''' clas ...

  3. python全栈开发day113-DBUtils(pymysql数据连接池)、Request管理上下文分析

    1.DBUtils(pymysql数据连接池) import pymysql from DBUtils.PooledDB import PooledDB POOL = PooledDB( creato ...

  4. Python 使用 PyMysql、DBUtils 创建连接池,提升性能

    转自:https://blog.csdn.net/weixin_41287692/article/details/83413775 Python 编程中可以使用 PyMysql 进行数据库的连接及诸如 ...

  5. MySQL 使用连接池封装pymysql

    备注:1,记得先修改连接的数据库哦,(用navicat更方便一点):2,分开两个py文件写入,运行sqlhelper.py文件 一.在utils.py中写 import pymysqlfrom DBU ...

  6. 连接池的实现 redis例子

    # -*- encoding:utf-8 -*- # import pymysql # # conn = pymysql.connect(host="127.0.0.1", por ...

  7. 利用python list 完成最简单的DB连接池

    先来看查看效果: 在代码连接数据库后,并且执行三条sql后,将mysql直接重启掉,故我们的连接池连接均是不ok的,所以,它会全部删除再抓新的连接下来,重启mysql命令: 关于python代码: # ...

  8. DBUtils--数据库连接池

    介绍 DBUtils是一套Python数据库连接池包,并允许对非线程安全的数据库接口进行线程安全包装. pg大概是是PostgreSQL(基于PyGreSQL)数据库,DB是其他数据库 Steady[ ...

  9. 深入研究sqlalchemy连接池

    简介: 相对于最新的MySQL5.6,MariaDB在性能.功能.管理.NoSQL扩展方面包含了更丰富的特性.比如微秒的支持.线程池.子查询优化.组提交.进度报告等. 本文就主要探索MariaDB当中 ...

随机推荐

  1. 5、python第一天作业

    作业一:编写登陆接口 1.输入用户名密码 2.认证成功后显示欢迎信息 3.输错三次后锁定 分析: 1.流程控制图 2.编写思路 以r+(读写模式)打开文件,读取文件内容字符串,再写入文件,以字符串的长 ...

  2. 第 31 章 项目实战-PC端固定布局[1]

    学习要点: 1.准备工作 2.创建项目 3.网站结构 4.CSS选择器 5.完成导航 主讲教师:李炎恢 本章主要开始使用学习用HTML5和CSS3来构建Web页面,第一个项目采用PC端 固定布局来实现 ...

  3. Spring Boot中静态资源(JS, 图片)等应该放在什么位置

    Spring Boot的静态资源,比如图片应该放在什么位置呢, 如果你放在传统WEB共的类似地方, 比如webapp或者WEB-INF下,你会得到一张示意文件未找到的破碎图片.那应该放哪里呢? 百度一 ...

  4. 《Ext JS模板与组件基本知识框架图----模板》

    最近在整理Ext JS的模板和组件,在参考<Ext JS权威指南>,<Ext JS Web应用程序开发指南>,<Ext JS API>等相关书籍后才写下这篇< ...

  5. 【英语学习】2016.09.11 Culture Insider: Teacher's Day in ancient China

      Culture Insider: Teacher's Day in ancient China 2016-09-10 CHINADAILY Today is the 32nd Chinese Te ...

  6. javascript随笔20160808

    var jsondata=$.parseJSON(@ViewBag.x); //转换为Json数据 var fruits = ["Banana", "Orange&quo ...

  7. Snort 安装 配置 - Archlinux

    About Snort Snort是一套开放源代码(OpenSource and free)的网络入侵预防软件(NIPS)与网络入侵检测软件(NIDS).Snort使用了以侦测签名(signature ...

  8. 了解HTML 盒模型

    HTML在布局上, 有一个非常重要的模型, 那就是盒子模型, 在盒子模型中把标签内容理解为一个物品, 而css样式理解为包容着这个物品的盒子, 一般的块级标签都具有盒子模型的特征, 你可以在css中对 ...

  9. CSS3-05 样式 4

    前言 关于 CSS 的介绍,基本上告一段落了.在该博客中将介绍如何通过 CSS 去设置一个 HTML 元素,显示在 Web 页面的位置. 定位 概述 定义元素位置的基准,即:该元素与 HTML 文档流 ...

  10. [C#6] 6-表达式形式的成员函数

    0. 目录 C#6 新增特性目录 1. 老版本的代码 internal class Person { public string FirstName { get; set; } public stri ...