时间:2021-07-01 10:21:17 帮助过:11人阅读
存在问题:每一次请求就得创建数据库链接,可能我们处理数据只需要很少时间,而连接数据库却占了很长时间,每次请求反复创建数据库连接,连接数太多,造成数据库性能的损耗
场景二:
- 缺点,不能支持并发
- <span style="color: #0000ff">import</span><span style="color: #000000"> pymysql
- CONN</span>=<span style="color: #000000">pymysql.connect()
- </span><span style="color: #0000ff">def</span><span style="color: #000000"> index():
- cursor </span>=<span style="color: #000000"> CONN.cursor()
- cursor.execute(</span><span style="color: #800000">‘</span><span style="color: #800000">select * from tb where id > %s</span><span style="color: #800000">‘</span>,[5<span style="color: #000000">,])
- result </span>=<span style="color: #000000"> cursor.fetchall()
- cursor.close()
- CONN.close()
- </span><span style="color: #0000ff">print</span><span style="color: #000000">(result)
- </span><span style="color: #0000ff">def</span><span style="color: #000000"> update():</span><span style="color: #000000">
- cursor </span>=<span style="color: #000000"> CONN.cursor()
- cursor.execute(</span><span style="color: #800000">‘</span><span style="color: #800000">update userinfo set username=%s where id > %s</span><span style="color: #800000">‘</span>,[<span style="color: #800000">‘</span><span style="color: #800000">ctz</span><span style="color: #800000">‘</span>,5<span style="color: #000000">,])
- CONN.commit()
- cursor.close()
- CONN.close()
- </span><span style="color: #0000ff">return</span> <span style="color: #800000">‘</span><span style="color: #800000">hello</span><span style="color: #800000">‘</span>
把数据库链接放到常量中去了,这样就可以保证每次请求都只创建一次数据库链接,但是还是存在问题:不能支持并发
如果有多个线程进来的话要执行同一个函数,第一个线程还没执行玩,第二个线程进来啦,但是只有一个conn这时就报错;当然我们也可以用下面这种方式解决报错问题:
- <span style="color: #0000ff">import</span><span style="color: #000000"> threading
- LOCK</span>=<span style="color: #000000">threading.RLock()
- CONN</span>=<span style="color: #000000">pymysql.connect()
- </span><span style="color: #0000ff">def</span><span style="color: #000000"> index():
- with LOCK:
- cursor </span>=<span style="color: #000000"> CONN.cursor()
- cursor.execute(</span><span style="color: #800000">‘</span><span style="color: #800000">select * from tb where id > %s</span><span style="color: #800000">‘</span>, [5<span style="color: #000000">, ])
- result </span>=<span style="color: #000000"> cursor.fetchall()
- cursor.close()
- </span><span style="color: #0000ff">print</span>(result)
但是 这样虽然解决了多个线程竞争报错的问题,但是程序加锁后变成了串行,运行效率就变低了
为了解决上面两个场景出现的问题,我们找到了解决的办法,就是创建数据库连接池
DBUtils是Python的一个用于实现数据库连接池的模块。
此连接池有两种连接模式:
实现原理:基于threaing.local实现为每一个线程创建一个链接,该线程关闭时,不是真正关闭;本线程再次调用时,还是使用的最开始创建的连接。直到线程终止,数据库连接才关闭
- <span style="color: #800000">"""</span><span style="color: #800000">
- 为每个线程创建一个连接,thread.local实现。
- </span><span style="color: #800000">"""</span>
- <span style="color: #0000ff">from</span> DBUtils.PersistentDB <span style="color: #0000ff">import</span><span style="color: #000000"> PersistentDB
- </span><span style="color: #0000ff">import</span><span style="color: #000000"> pymysql
- POOL </span>=<span style="color: #000000"> PersistentDB(
- creator</span>=pymysql, <span style="color: #008000">#</span><span style="color: #008000"> 使用链接数据库的模块</span>
- maxusage=None, <span style="color: #008000">#</span><span style="color: #008000"> 一个链接最多被重复使用的次数,None表示无限制</span>
- setsession=[], <span style="color: #008000">#</span><span style="color: #008000"> 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]</span>
- ping=<span style="color: #000000">0,
- </span><span style="color: #008000">#</span><span style="color: #008000"> 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</span>
- closeable=<span style="color: #000000">False,
- </span><span style="color: #008000">#</span><span style="color: #008000"> 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接)</span>
- threadlocal=None, <span style="color: #008000">#</span><span style="color: #008000"> 本线程独享值得对象,用于保存链接对象,如果链接对象被重置</span>
- host=<span style="color: #800000">‘</span><span style="color: #800000">127.0.0.1</span><span style="color: #800000">‘</span><span style="color: #000000">,
- port</span>=3306<span style="color: #000000">,
- user</span>=<span style="color: #800000">‘</span><span style="color: #800000">root</span><span style="color: #800000">‘</span><span style="color: #000000">,
- password</span>=<span style="color: #800000">‘</span><span style="color: #800000">123</span><span style="color: #800000">‘</span><span style="color: #000000">,
- database</span>=<span style="color: #800000">‘</span><span style="color: #800000">pooldb</span><span style="color: #800000">‘</span><span style="color: #000000">,
- charset</span>=<span style="color: #800000">‘</span><span style="color: #800000">utf8</span><span style="color: #800000">‘</span><span style="color: #000000">
- )
- </span><span style="color: #0000ff">def</span><span style="color: #000000"> func():
- </span><span style="color: #008000">#</span><span style="color: #008000"> conn = SteadyDBConnection()</span>
- conn =<span style="color: #000000"> POOL.connection()
- cursor </span>=<span style="color: #000000"> conn.cursor()
- cursor.execute(</span><span style="color: #800000">‘</span><span style="color: #800000">select * from tb1</span><span style="color: #800000">‘</span><span style="color: #000000">)
- result </span>=<span style="color: #000000"> cursor.fetchall()
- cursor.close()
- conn.close() </span><span style="color: #008000">#</span><span style="color: #008000"> 不是真的关闭,而是假的关闭。 conn = pymysql.connect() conn.close()</span>
- <span style="color: #000000">
- conn </span>=<span style="color: #000000"> POOL.connection()
- cursor </span>=<span style="color: #000000"> conn.cursor()
- cursor.execute(</span><span style="color: #800000">‘</span><span style="color: #800000">select * from tb1</span><span style="color: #800000">‘</span><span style="color: #000000">)
- result </span>=<span style="color: #000000"> cursor.fetchall()
- cursor.close()
- conn.close()
- </span><span style="color: #0000ff">import</span><span style="color: #000000"> threading
- </span><span style="color: #0000ff">for</span> i <span style="color: #0000ff">in</span> range(10<span style="color: #000000">):
- t </span>= threading.Thread(target=<span style="color: #000000">func)
- t.start()</span>
模式二:创建一批连接到连接池,供所有线程共享使用,使用时来进行获取,使用完毕后,再次放回到连接池。
PS:由于pymysql、MySQLdb等threadsafety值为1,所以该模式连接池中的线程会被所有线程共享。
- <span style="color: #0000ff">import</span><span style="color: #000000"> time
- </span><span style="color: #0000ff">import</span><span style="color: #000000"> pymysql
- </span><span style="color: #0000ff">import</span><span style="color: #000000"> threading
- </span><span style="color: #0000ff">from</span> DBUtils.PooledDB <span style="color: #0000ff">import</span><span style="color: #000000"> PooledDB, SharedDBConnection
- POOL </span>=<span style="color: #000000"> PooledDB(
- creator</span>=pymysql, <span style="color: #008000">#</span><span style="color: #008000"> 使用链接数据库的模块</span>
- maxconnections=6, <span style="color: #008000">#</span><span style="color: #008000"> 连接池允许的最大连接数,0和None表示不限制连接数</span>
- mincached=2, <span style="color: #008000">#</span><span style="color: #008000"> 初始化时,链接池中至少创建的空闲的链接,0表示不创建</span>
- <span style="color: #000000">
- maxcached</span>=5, <span style="color: #008000">#</span><span style="color: #008000"> 链接池中最多闲置的链接,0和None不限制</span>
- maxshared=3, <span style="color: #008000">#</span><span style="color: #008000"> 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。</span>
- blocking=True, <span style="color: #008000">#</span><span style="color: #008000"> 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错</span>
- maxusage=None, <span style="color: #008000">#</span><span style="color: #008000"> 一个链接最多被重复使用的次数,None表示无限制</span>
- setsession=[], <span style="color: #008000">#</span><span style="color: #008000"> 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]</span>
- ping=<span style="color: #000000">0,
- </span><span style="color: #008000">#</span><span style="color: #008000"> 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</span>
- host=<span style="color: #800000">‘</span><span style="color: #800000">127.0.0.1</span><span style="color: #800000">‘</span><span style="color: #000000">,
- port</span>=3306<span style="color: #000000">,
- user</span>=<span style="color: #800000">‘</span><span style="color: #800000">root</span><span style="color: #800000">‘</span><span style="color: #000000">,
- password</span>=<span style="color: #800000">‘</span><span style="color: #800000">123</span><span style="color: #800000">‘</span><span style="color: #000000">,
- database</span>=<span style="color: #800000">‘</span><span style="color: #800000">pooldb</span><span style="color: #800000">‘</span><span style="color: #000000">,
- charset</span>=<span style="color: #800000">‘</span><span style="color: #800000">utf8</span><span style="color: #800000">‘</span><span style="color: #000000">
- )
- </span><span style="color: #0000ff">def</span><span style="color: #000000"> func():
- </span><span style="color: #008000">#</span><span style="color: #008000"> 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常</span>
- <span style="color: #008000">#</span><span style="color: #008000"> 否则</span>
- <span style="color: #008000">#</span><span style="color: #008000"> 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。</span>
- <span style="color: #008000">#</span><span style="color: #008000"> 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。</span>
- <span style="color: #008000">#</span><span style="color: #008000"> 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。</span>
- <span style="color: #008000">#</span><span style="color: #008000"> 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。</span>
- <span style="color: #008000">#</span><span style="color: #008000"> PooledDedicatedDBConnection</span>
- conn =<span style="color: #000000"> POOL.connection()
- </span><span style="color: #008000">#</span><span style="color: #008000"> print(th, ‘链接被拿走了‘, conn1._con)</span>
- <span style="color: #008000">#</span><span style="color: #008000"> print(th, ‘池子里目前有‘, pool._idle_cache, ‘\r\n‘)</span>
- <span style="color: #000000">
- cursor </span>=<span style="color: #000000"> conn.cursor()
- cursor.execute(</span><span style="color: #800000">‘</span><span style="color: #800000">select * from tb1</span><span style="color: #800000">‘</span><span style="color: #000000">)
- result </span>=<span style="color: #000000"> cursor.fetchall()
- conn.close()
- conn </span>=<span style="color: #000000"> POOL.connection()
- </span><span style="color: #008000">#</span><span style="color: #008000"> print(th, ‘链接被拿走了‘, conn1._con)</span>
- <span style="color: #008000">#</span><span style="color: #008000"> print(th, ‘池子里目前有‘, pool._idle_cache, ‘\r\n‘)</span>
- <span style="color: #000000">
- cursor </span>=<span style="color: #000000"> conn.cursor()
- cursor.execute(</span><span style="color: #800000">‘</span><span style="color: #800000">select * from tb1</span><span style="color: #800000">‘</span><span style="color: #000000">)
- result </span>=<span style="color: #000000"> cursor.fetchall()
- conn.close()
- func()</span>
PS:
- <span style="color: #000000">如果有三个线程来连接池中获取链接:
- 1个链接可以为三个线程提供服务
- 2个链接可以为三个线程提供服务
- 3个链接可以为三个线程提供服务
- maxshared在使用pymysql</span>/<span style="color: #000000">mysqldb时均无用,因为他们的threadsafety都是1,而查看PooledDB源码就可以知道
- </span><span style="color: #0000ff">if</span> threadsafety > 1 <span style="color: #0000ff">and</span><span style="color: #000000"> maxshared:
- self._maxshared </span>=<span style="color: #000000"> maxshared
- self._shared_cache </span>= [] <span style="color: #008000">#</span><span style="color: #008000"> the cache for shared connections</span>
- <span style="color: #0000ff">else</span><span style="color: #000000">:
- self._maxshared </span>=<span style="color: #000000"> 0
- 即使设置了 永远都是0</span>
Python 数据库连接池DButils
标签:多个 命令 数据库链接 加锁 mys blocking 有一个 重置 重复