Python 数据库连接池DButils

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

场景一:

缺点:每次请求反复创建数据库连接,连接数太多
import pymysql
def index():
    conn = pymysql.connect()
    cursor = conn.cursor()
    cursor.execute('select * from tb where id > %s',[5,])
    result = cursor.fetchall()
    cursor.close()
    conn.close()
    print(result)
    
    
def update():
    # 第一步:缺点:每次请求反复创建数据库连接,连接数太多
    conn = pymysql.connect()
    cursor = conn.cursor()
    cursor.execute('update userinfo set username=%s where id > %s',['ctz',5,])
    conn.commit()
    cursor.close()
    conn.close()
    return  'hello'

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

 

场景二:

缺点,不能支持并发
import pymysql
CONN=pymysql.connect()
def index():
    cursor = CONN.cursor()
    cursor.execute('select * from tb where id > %s',[5,])
    result = cursor.fetchall()
    cursor.close()
    CONN.close()
    print(result)


def update():
    cursor = CONN.cursor()
    cursor.execute('update userinfo set username=%s where id > %s',['ctz',5,])
    CONN.commit()
    cursor.close()
    CONN.close()
    return  'hello'

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

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

import threading
LOCK=threading.RLock()
CONN=pymysql.connect()
def index():
    with LOCK:
        cursor = CONN.cursor()
        cursor.execute('select * from tb where id > %s', [5, ])
        result = cursor.fetchall()
        cursor.close()
    
        print(result)

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

 

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

 

数据库链接池

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

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

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

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

"""
为每个线程创建一个连接,thread.local实现。
"""

from DBUtils.PersistentDB import PersistentDB
import pymysql

POOL = PersistentDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
    setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
    ping=0,
    # 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
    closeable=False,
    # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接)
    threadlocal=None,  # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置
    host='127.0.0.1',
    port=3306,
    user='root',
    password='123',
    database='pooldb',
    charset='utf8'
)


def func():
    # conn = SteadyDBConnection()
    conn = POOL.connection()
    cursor = conn.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    cursor.close()
    conn.close() # 不是真的关闭,而是假的关闭。 conn = pymysql.connect()   conn.close()

    conn = POOL.connection()
    cursor = conn.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    cursor.close()
    conn.close()

import threading

for i in range(10):
    t = threading.Thread(target=func)
    t.start()

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

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

import time
import pymysql
import threading
from DBUtils.PooledDB import PooledDB, SharedDBConnection
POOL = PooledDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建


    maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
    maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
    blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
    maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
    setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
    ping=0,
    # 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
    host='127.0.0.1',
    port=3306,
    user='root',
    password='123',
    database='pooldb',
    charset='utf8'
)


def func():
    # 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常
    # 否则
    # 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。
    # 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。
    # 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。
    # 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。

    # PooledDedicatedDBConnection
    conn = POOL.connection()

    # print(th, '链接被拿走了', conn1._con)
    # print(th, '池子里目前有', pool._idle_cache, '\r\n')

    cursor = conn.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    conn.close()





    conn = POOL.connection()

    # print(th, '链接被拿走了', conn1._con)
    # print(th, '池子里目前有', pool._idle_cache, '\r\n')

    cursor = conn.cursor()
    cursor.execute('select * from tb1')
    result = cursor.fetchall()
    conn.close()


func()

PS:

如果有三个线程来连接池中获取链接:
   1个链接可以为三个线程提供服务
   2个链接可以为三个线程提供服务
   3个链接可以为三个线程提供服务


maxshared在使用pymysql/mysqldb时均无用,因为他们的threadsafety都是1,而查看PooledDB源码就可以知道
        if threadsafety > 1 and maxshared:
            self._maxshared = maxshared
            self._shared_cache = []  # the cache for shared connections
        else:
            self._maxshared = 0

即使设置了 永远都是0

 

posted @ 2018-01-06 14:10  A-a  阅读(...)  评论(...编辑  收藏