mysql连接池, 连接的释放, 连接的回收, 连接释放机制

本文探讨了MySQL连接池的使用,特别是在SQLAlchemy中遇到的问题。当线程数小于连接数时,若连接耗尽,系统会立即报告错误而非阻塞所有线程。在特定条件下,如开启防火墙屏蔽IP,执行会阻塞,但连接仍能自动释放和回收。然而,不显式关闭连接可能导致连接无法正常回收。通过分析DBUtils.PooledDB的源码,发现重入锁(RLock)在建立或获取连接时未释放可能导致所有线程阻塞。文中提供了模拟代码以说明这一现象。
Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

#!/bin/python

from sqlalchemy.pool import QueuePool
import threading
import MySQLdb
import time

def creator():
    c = MySQLdb.connect(host="192.168.41.76", db="test", user="root", passwd="root", port=3306)
    return c

pool = QueuePool(creator, pool_size=2, max_overflow=0, timeout=1)

class Demo(object):

    def proc(self):
        ident = threading.currentThread().getName()
        conn = pool.connect()
        print ident, id(conn), id(conn.connection)
        cur = conn.cursor()
        cur.execute("select * from t_user")
        res = cur.fetchall()
        # conn.close()
        time.sleep(2)
    def __del__(self):
        print "__del__"

def target():
    while True:
        try:
            ident = threading.currentThread().getName()
            d = Demo()
            d.proc()
            # conn = pool.connect()
            # print ident, id(conn)
            # cur = conn.cursor()
            # cur.execute("select * from t_user")
            # res = cur.fetchall() 
            # # conn.close()
            # time.sleep(2)
        except Exception,e:
             print ident, e

def main():
    for i in range(3):
        t = threading.Thread(target=target)
        t.setDaemon(True)
        t.start()
    while True:
        try:
            time.sleep(10)
        except:
            import sys,traceback
            print traceback.format_exc()
            sys.exit(1)

if __name__ == "__main__":
    main()

连接池sqlalchemy使用模拟引发下面问题: 

线程数小于连接数, 当连接数耗光, 不至于所有的线程都堵死在获取连接上, 而是立马报错连接不够

连接获取之后, 如果打开防火墙屏蔽ip, 会阻塞在execute上面, 这个是mysql的特性

这种情况下, 连接是可以自动释放回收的, 即使不写conn.close()

如果屏蔽Demo类的使用, 打开下面注释, 不显示写conn.close() 则连接无法释放回收



下面代码解释自动回收conn

# coding=gbk
#!/bin/python

import weakref
import threading
import Queue
import time
import MySQLdb



class ObjFairy(object):
    """封装相关对象, 方便回收的时候操作
    """
    def __init__(self, _pool, _echo):
        self.pool = _pool
        self.echo = _echo

def callback(ref):
    """弱引用在gc的时候callback, ref为回收对象
    """
    print "gc callback, do clean", ref

class Pool(object):
    """xx池(比如sqlalchemy的QueuePool)
    """

    @staticmethod
    def checkout():
        fairy = ObjFairy(None, None)
        current = weakref.ref(fairy, callback)
        return current



# =========测试========== #
class DemoBusi(object):
    """业务类
    """

    def proc(self):
        current = Pool.checkout()  # 取出某可重复使用对象,  无需显示清理

def target():
    while True:
        demo = DemoBusi()  # 模拟业务handler, 一次请求过来, new BusiHandler
        demo.proc()
        # current = Pool.checkout(None)
        time.sleep(1)

def main():
    for i in range(3):
        t = threading.Thread(target=target)
        t.setDaemon(True)
        t.start()

    while True:
        time.sleep(2 * 60)
        break

if __name__ == "__main__":
    main()



sqlalchemy连接池源码

@classmethod
def checkout(cls, pool):
	rec = pool._do_get()
	try:
	    dbapi_connection = rec.get_connection()
	except:
	    with util.safe_reraise():
		rec.checkin()
	echo = pool._should_log_debug()
	fairy = _ConnectionFairy(dbapi_connection, rec, echo)
	rec.fairy_ref = weakref.ref(
	    fairy,
	    lambda ref: _finalize_fairy and
	    _finalize_fairy(
		dbapi_connection,
		rec, pool, ref, echo)
	)
	_refs.add(rec)
	if echo:
	    pool.logger.debug("Connection %r checked out from pool",
			      dbapi_connection)
	return fairy


跟踪DBUtils.py里面的PooledDB源码发现, 3个线程2个连接会导致所有的线程阻塞,是因为在建立连接或者获取连接的时候阻塞, 导致重入锁不释放(RLock)

下面模拟代码

#coding=utf8

import threading
from threading import Condition
import time

class Obj(object):

    def __init__(self):
        self.bt = time.time()
        self.cond = Condition()

obj = Obj()
class Demo(object):
    def __init__(self):
        pass

    def proc(self):
        et = time.time()
        if et - obj.bt > 6:
            print "block(%s)" % threading.currentThread().getName()
            raw_input()
        time.sleep(1)
        print "proc(%s)" % threading.currentThread().getName()

def f():
    while 1:
        print "----acquire(%s)" % threading.currentThread().getName()
        obj.cond.acquire()
        Demo().proc()
        obj.cond.release()
        time.sleep(0.1)
        print "-----release(%s)" % threading.currentThread().getName()
        

def main():
    for i in range(3):
        t = threading.Thread(target=f)
        t.setDaemon(True)
        time.sleep(2)
        t.start()
    time.sleep(10000)

if __name__ == "__main__":
    main()


这个是源码

self._condition.acquire()
try:
while (self._maxconnections
	and self._connections >= self._maxconnections):
    self._condition.wait()
# connection limit not reached, get a dedicated connection
try: # first try to get it from the idle cache
    con = self._idle_cache.pop(0)
except IndexError: # else get a fresh connection
    con = self.steady_connection()
else:
    con._ping_check() # check connection
con = PooledDedicatedDBConnection(self, con)
self._connections += 1
finally:
self._condition.release()


您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值