python 数据库连接池DButils

╰半夏微凉° 2021-11-06 00:42 538阅读 0赞

常规的数据库链接存在的问题:

场景一:

  1. 缺点:每次请求反复创建数据库连接,连接数太多

复制代码

  1. import pymysql
  2. def index():
  3. conn = pymysql.connect()
  4. cursor = conn.cursor()
  5. cursor.execute('select * from tb where id > %s',[5,])
  6. result = cursor.fetchall()
  7. cursor.close()
  8. conn.close()
  9. print(result)
  10. def update():
  11. # 第一步:缺点:每次请求反复创建数据库连接,连接数太多
  12. conn = pymysql.connect()
  13. cursor = conn.cursor()
  14. cursor.execute('update userinfo set username=%s where id > %s',['ctz',5,])
  15. conn.commit()
  16. cursor.close()
  17. conn.close()
  18. return 'hello'

复制代码

存在问题:每一次请求就得创建数据库链接,可能我们处理数据只需要很少时间,而连接数据库却占了很长时间,每次请求反复创建数据库连接,连接数太多,造成数据库性能的损耗

场景二:

  1. 缺点,不能支持并发

复制代码

  1. import pymysql
  2. CONN=pymysql.connect()
  3. def index():
  4. cursor = CONN.cursor()
  5. cursor.execute('select * from tb where id > %s',[5,])
  6. result = cursor.fetchall()
  7. cursor.close()
  8. CONN.close()
  9. print(result)
  10. def update():
  11. cursor = CONN.cursor()
  12. cursor.execute('update userinfo set username=%s where id > %s',['ctz',5,])
  13. CONN.commit()
  14. cursor.close()
  15. CONN.close()
  16. return 'hello'

复制代码

把数据库链接放到常量中去了,这样就可以保证每次请求都只创建一次数据库链接,但是还是存在问题:不能支持并发

如果有多个线程进来的话要执行同一个函数,第一个线程还没执行玩,第二个线程进来啦,但是只有一个conn这时就报错;当然我们也可以用下面这种方式解决报错问题:

复制代码

  1. import threading
  2. LOCK=threading.RLock()
  3. CONN=pymysql.connect()
  4. def index():
  5. with LOCK:
  6. cursor = CONN.cursor()
  7. cursor.execute('select * from tb where id > %s', [5, ])
  8. result = cursor.fetchall()
  9. cursor.close()
  10. print(result)

复制代码

但是 这样虽然解决了多个线程竞争报错的问题,但是程序加锁后变成了串行,运行效率就变低了

为了解决上面两个场景出现的问题,我们找到了解决的办法,就是创建数据库连接池

数据库链接池

DBUtils是Python的一个用于实现数据库连接池的模块。

此连接池有两种连接模式:

  • 模式一:为每个线程创建一个连接,线程即使调用了close方法,也不会关闭,只是把连接重新放到连接池,供自己线程再次使用。当线程终止时,连接自动关闭。

    实现原理:基于threaing.local实现为每一个线程创建一个链接,该线程关闭时,不是真正关闭;本线程再次调用时,还是使用的最开始创建的连接。直到线程终止,数据库连接才关闭

复制代码

  1. """
  2. 为每个线程创建一个连接,thread.local实现。
  3. """
  4. from DBUtils.PersistentDB import PersistentDB
  5. import pymysql
  6. POOL = PersistentDB(
  7. creator=pymysql, # 使用链接数据库的模块
  8. maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制
  9. setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
  10. ping=0,
  11. # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
  12. closeable=False,
  13. # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接)
  14. threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置
  15. host='127.0.0.1',
  16. port=3306,
  17. user='root',
  18. password='123',
  19. database='pooldb',
  20. charset='utf8'
  21. )
  22. def func():
  23. # conn = SteadyDBConnection()
  24. conn = POOL.connection()
  25. cursor = conn.cursor()
  26. cursor.execute('select * from tb1')
  27. result = cursor.fetchall()
  28. cursor.close()
  29. conn.close() # 不是真的关闭,而是假的关闭。 conn = pymysql.connect() conn.close()
  30. conn = POOL.connection()
  31. cursor = conn.cursor()
  32. cursor.execute('select * from tb1')
  33. result = cursor.fetchall()
  34. cursor.close()
  35. conn.close()
  36. import threading
  37. for i in range(10):
  38. t = threading.Thread(target=func)
  39. t.start()

复制代码

模式二:创建一批连接到连接池,供所有线程共享使用,使用时来进行获取,使用完毕后,再次放回到连接池。

PS:由于pymysql、MySQLdb等threadsafety值为1,所以该模式连接池中的线程会被所有线程共享。

复制代码

  1. import time
  2. import pymysql
  3. import threading
  4. from DBUtils.PooledDB import PooledDB, SharedDBConnection
  5. POOL = PooledDB(
  6. creator=pymysql, # 使用链接数据库的模块
  7. maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数
  8. mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
  9. maxcached=5, # 链接池中最多闲置的链接,0和None不限制
  10. maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
  11. blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
  12. maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制
  13. setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
  14. ping=0,
  15. # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
  16. host='127.0.0.1',
  17. port=3306,
  18. user='root',
  19. password='123',
  20. database='pooldb',
  21. charset='utf8'
  22. )
  23. def func():
  24. # 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常
  25. # 否则
  26. # 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。
  27. # 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。
  28. # 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。
  29. # 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。
  30. # PooledDedicatedDBConnection
  31. conn = POOL.connection()
  32. # print(th, '链接被拿走了', conn1._con)
  33. # print(th, '池子里目前有', pool._idle_cache, '\r\n')
  34. cursor = conn.cursor()
  35. cursor.execute('select * from tb1')
  36. result = cursor.fetchall()
  37. conn.close()
  38. conn = POOL.connection()
  39. # print(th, '链接被拿走了', conn1._con)
  40. # print(th, '池子里目前有', pool._idle_cache, '\r\n')
  41. cursor = conn.cursor()
  42. cursor.execute('select * from tb1')
  43. result = cursor.fetchall()
  44. conn.close()
  45. func()

复制代码

PS:

复制代码

  1. 如果有三个线程来连接池中获取链接:
  2. 1个链接可以为三个线程提供服务
  3. 2个链接可以为三个线程提供服务
  4. 3个链接可以为三个线程提供服务
  5. maxshared在使用pymysql/mysqldb时均无用,因为他们的threadsafety都是1,而查看PooledDB源码就可以知道
  6. if threadsafety > 1 and maxshared:
  7. self._maxshared = maxshared
  8. self._shared_cache = [] # the cache for shared connections
  9. else:
  10. self._maxshared = 0
  11. 即使设置了 永远都是0

复制代码

转载于:https://www.cnblogs.com/Xanderzyl/p/11000335.html

发表评论

表情:
评论列表 (有 0 条评论,538人围观)

还没有评论,来说两句吧...

相关阅读