Python数据库连接池DBUtils

1 问题由来

前一段时间用Mysqldb模块进行数据库的开发。共用一个数据库连接:


import MySQLdb as mdb

    def create_single_conn(self):
        self._mysql_connection = mdb.connect(
            '%s' % conf.db['host'],
            '%s' % conf.db['user'],
            '%s' % conf.db['password'],
            'test',
            cursorclass=mdb.cursors.DictCursor
        )

在程序运行过程中,出现错误:
OperationalError: (2006, ‘MySQL server has gone away’)

查找原因:是数据连接断开了
想到的解决方法:
1 设置连接时间

  1. 设置mysql数据的连接时间
    在日常的MySQL服务器中,wait-timeout这个参数非常有用。
    (在部分MySQL的默认配置中可能没有wait-timeout这个参数项,你在[mysqld]节中加上即可)
    参数意义:MySQL客户端的数据库连接闲置最大时间值。
    参数默认值:默认值为8小时 。
    这个参数的大概意思是某一个MySQL客户端连接闲置的最大时间值,即某一个MySQL客户端连接过程中,闲置的最大时间到达后服务器将其关闭。
    MySQL服务器所支撑的最大连接数是有限的,因为每一个连接、第一个表打开的操作都要消耗服务器内存,理想状态是当一个MySQL客户端连接完成工作就自动断开释放内存

如果在配置文件my.cnf中只设置参数wait_timeout=100,则重启服务器后进入,执行:
Mysql> show variables like “%timeout%”;
会发现参数设置并未生效,仍然为28800(即默认的8个小时)。
查询资料后,要同时设置interactive_timeout和wait_timeout才会生效。
参考:http://blog.csdn.net/cenfei78325747/article/details/7854611
2. 设置mysqldb的连接时间
connect_timeout:连接超时的时间,单位秒

def mysql_conn():
  try:
  conn = MySQLdb.connect(host = '192.168.8.100',user = 'mysql',passwd = '123456',connect_timeout=10)
  cursor = conn.cursor()
  sql = "SELECT COUNT(1) FROM mysql.user"
  cursor.execute(sql)
  alldata = cursor.fetchall()
  count = alldata[0][0]
  cursor.close()
  conn.close()
  print count
  except Exception,e:

2 使用mysqldb自带的conn.ping()

def executeSQL(self,sql=""):
        try:
            self.conn.ping()
        except Exception,e:
            self.log.error("Msql出了问题")
            self.log.error(str(e))
            while True:
                try:
                    self.conn = MySQLdb.connect(self.config.get('mysql_server'),self.config.get('mysql_user'),self.config.get('mysql_pass'),self.config.get('mysql_db_name'),connect_timeout=60,compress=True,charset="UTF8")
                    break
                except Exception,e:
                    self.log.error("尝试重连接失败")
                    time.sleep(2)
                    continue
            self.cursor=self.conn.cursor()
        try:
            self.cursor.execute(sql)
            self.conn.commit()
            return 1
        except Exception,e:
            self.log.error(str(e))
            return 0

参考:https://lightless.me/archives/mysqldb-lost-connection-to-mysql.html
http://blog.chinaunix.net/uid-25525723-id-2981174.html

3 使用DBUtils建立数据库连接池
DBUtils 是一套允许线程化 Python 程序可以安全和有效的访问数据库的模块。

2 下载安装

2.1 从pypi下载
https://pypi.python.org/pypi/DBUtils

$ wget https://pypi.python.org/packages/65/65/89afee016aca7fbb5c1642e6ef3864d80af808dc5efa7367b328093eece9/DBUtils-1.1.tar.gz

2.2 安装
方法1:

cd DBUtils-1.1
python setup.py install

方法2:使用pip

$ pip search DBUtils
DBUtils                   - Database connections for multi-threaded environments.

3 介绍

The DBUtils suite is realized as a Python package containing two subsets of modules, one for use with arbitrary DB-API 2 modules, the other one for use with the classic PyGreSQL module.

The dependencies of the modules in the universal DB-API 2 variant are as indicated in the following diagram:
这里写图片描述
The dependencies of the modules in the classic PyGreSQL variant are similar:
这里写图片描述

DBUtils是一套Python数据库连接池包,并允许对非线程安全的数据库接口进行线程安全包装。DBUtils来自Webware for Python。

DBUtils提供两种外部接口:
    PersistentDB :提供线程专用的数据库连接,并自动管理连接。
    PooledDB :提供线程间可共享的数据库连接,并自动管理连接。

实测证明 PersistentDB 的速度是最高的,但是在某些特殊情况下,数据库的连接过程可能异常缓慢,而此时的PooledDB则可以提供相对来说平均连接时间比较短的管理方式。

另外,实际使用的数据库驱动也有所依赖,比如SQLite数据库只能使用PersistentDB作连接池。

功能
SteadyDB.py用于稳定数据库连接

PooledDB.py连接池

PersistentDB.py维持持续的数据库连接(持续性连接)

SimplePooledDB.py简单连接池

SimplePooledDB
DBUtils.SimplePooledDB 是一个非常简单的数据库连接池实现。他比完善的 PooledDB 模块缺少很多功能。 DBUtils.SimplePooledDB 本质上类似于 MiscUtils.DBPool 这个Webware的组成部分。你可以把它看作一种演示程序。

SteadyDB

DBUtils.SteadyDB 是一个模块实现了”强硬”的数据库连接,基于DB-API 2建立的原始连接。一个”强硬”的连接意味着在连接关闭之后,或者使用次数操作限制时会重新连接。

一个典型的例子是数据库重启时,而你的程序仍然在运行并需要访问数据库,或者当你的程序连接了一个防火墙后面的远程数据库,而防火墙重启时丢失了状态时。

一般来说你不需要直接使用 SteadyDB 它只是给接下来的两个模块提供基本服务, PersistentDB 和 PooledDB 。

PersistentDB

DBUtils.PersistentDB 实现了强硬的、线程安全的、顽固的数据库连接,使用DB-API 2模块。如下图展示了使用 PersistentDB 时的连接层步骤:

当一个线程首次打开一个数据库连接时,一个连接会打开并仅供这个线程使用。当线程关闭连接时,连接仍然持续打开供这个线程下次请求时使用这个已经打开的连接。连接在线程死亡时自动关闭。

简单的来说 PersistentDB 尝试重用数据库连接来提高线程化程序的数据库访问性能,并且他确保连接不会被线程之间共享。

因此, PersistentDB 可以在底层DB-API模块并非线程安全的时候同样工作的很好,并且他会在其他线程改变数据库会话或者使用多语句事务时同样避免问题的发生。

PooledDB

DBUtils.PooledDB 实现了一个强硬的、线程安全的、有缓存的、可复用的数据库连接,使用任何DB-API 2模块。如下图展示了使用 PooledDB 时的工作流程:

PooledDB 可以在不同线程之间共享打开的数据库连接。这在你连接并指定 maxshared 参数,并且底层的DB-API 2接口是线程安全才可以,但是你仍然可以使用专用数据库连接而不在线程之间共享连接。除了共享连接以外,还可以设立一个至少 mincached 的连接池,并且最多允许使用 maxcached 个连接,这可以同时用于专用和共享连接池。当一个线程关闭了一个非共享连接,则会返还到空闲连接池中等待下次使用。

如果底层DB-API模块是非线程安全的,线程锁会确保使用 PooledDB 是线程安全的。所以你并不需要为此担心,但是你在使用专用连接来改变数据库会话或执行多命令事务时必须小心。
该选择哪一个?

PersistentDB 和 PooledDB 都是为了重用数据库连接来提高性能,并保持数据库的稳定性。

所以选择何种模块,可以参考上面的解释。 PersistentDB 将会保持一定数量的连接供频繁使用。在这种情况下你总是保持固定数量的连接。如果你的程序频繁的启动和关闭线程,最好使用 PooledDB 。后面将会提到更好的调整,尤其在使用线程安全的DB-API 2模块时。

当然,这两个模块的接口是很相似的,你可以方便的在他们之间转换,并查看哪个更好一些。

官方指南:https://cito.github.io/w4py-olde-docs/Webware/DBUtils/Docs/UsersGuide.html

4 使用

连接池对象只初始化一次,一般可以作为模块级代码来确保。 PersistentDB的连接例子:

import DBUtils.PersistentDB
persist=DBUtils.PersistentDB.PersistentDB(dbpai=MySQLdb,maxusage=1000,**kwargs)
这里的参数dbpai指使用的底层数据库模块,兼容DB-API的。maxusage则为一个连接最大使用次数,参考了官方例子。后面的**kwargs则为实际传递给MySQLdb的参数。

获取连接: conn=persist.connection() 实际编程中用过的连接直接关闭 conn.close() 即可将连接交还给连接池。

PooledDB使用方法同PersistentDB,只是参数有所不同。

  • dbapi :数据库接口
  • mincached :启动时开启的空连接数量
  • maxcached :连接池最大可用连接数量
  • maxshared :连接池最大可共享连接数量
  • maxconnections :最大允许连接数量
  • blocking :达到最大数量时是否阻塞
  • maxusage :单个连接最大复用次数
  • setsession :用于传递到数据库的准备会话,如 [”set name UTF-8″] 。
# -*- coding: UTF-8 -*-
"""
desc:数据库操作类
@note:
1、执行带参数的SQL时,请先用sql语句指定需要输入的条件列表,然后再用tuple/list进行条件批配
2、在格式SQL中不需要使用引号指定数据类型,系统会根据输入参数自动识别
3、在输入的值中不需要使用转意函数,系统会自动处理
"""

import MySQLdb
from MySQLdb.cursors import DictCursor
from DBUtils.PooledDB import PooledDB

import Config

"""
Config是一些数据库的配置文件
"""

class Mysql(object):
    """
        MYSQL数据库对象,负责产生数据库连接 , 此类中的连接采用连接池实现
        获取连接对象:conn = Mysql.getConn()
        释放连接对象;conn.close()或del conn
    """
    #连接池对象
    __pool = None
    def __init__(self):
        """
        数据库构造函数,从连接池中取出连接,并生成操作游标
        """
#        self._conn = MySQLdb.connect(host=Config.DBHOST , port=Config.DBPORT , user=Config.DBUSER , passwd=Config.DBPWD ,
#                              db=Config.DBNAME,use_unicode=False,charset=Config.DBCHAR,cursorclass=DictCursor)
        self._conn = Mysql.__getConn()
        self._cursor = self._conn.cursor()

    @staticmethod
    def __getConn():
        """
        @summary: 静态方法,从连接池中取出连接
        @return MySQLdb.connection
        """
        if Mysql.__pool is None:
            __pool = PooledDB(creator=MySQLdb, mincached=1 , maxcached=20 ,
                              host=Config.DBHOST , port=Config.DBPORT , user=Config.DBUSER , passwd=Config.DBPWD ,
                              db=Config.DBNAME,use_unicode=False,charset=Config.DBCHAR,cursorclass=DictCursor)
        return __pool.connection()

    def getAll(self,sql,param=None):
        """
        @summary: 执行查询,并取出所有结果集
        @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
        @param param: 可选参数,条件列表值(元组/列表)
        @return: result list/boolean 查询到的结果集
        """
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql,param)
        if count>0:
            result = self._cursor.fetchall()
        else:
            result = False
        return result

    def getOne(self,sql,param=None):
        """
        @summary: 执行查询,并取出第一条
        @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
        @param param: 可选参数,条件列表值(元组/列表)
        @return: result list/boolean 查询到的结果集
        """
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql,param)
        if count>0:
            result = self._cursor.fetchone()
        else:
            result = False
        return result

    def getMany(self,sql,num,param=None):
        """
        @summary: 执行查询,并取出num条结果
        @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
        @param num:取得的结果条数
        @param param: 可选参数,条件列表值(元组/列表)
        @return: result list/boolean 查询到的结果集
        """
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql,param)
        if count>0:
            result = self._cursor.fetchmany(num)
        else:
            result = False
        return result

    def insertOne(self,sql,value):
        """
        @summary: 向数据表插入一条记录
        @param sql:要插入的SQL格式
        @param value:要插入的记录数据tuple/list
        @return: insertId 受影响的行数
        """
        self._cursor.execute(sql,value)
        return self.__getInsertId()

    def insertMany(self,sql,values):
        """
        @summary: 向数据表插入多条记录
        @param sql:要插入的SQL格式
        @param values:要插入的记录数据tuple(tuple)/list[list]
        @return: count 受影响的行数
        """
        count = self._cursor.executemany(sql,values)
        return count

    def __getInsertId(self):
        """
        获取当前连接最后一次插入操作生成的id,如果没有则为0
        """
        self._cursor.execute("SELECT @@IDENTITY AS id")
        result = self._cursor.fetchall()
        return result[0]['id']

    def __query(self,sql,param=None):
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql,param)
        return count

    def update(self,sql,param=None):
        """
        @summary: 更新数据表记录
        @param sql: SQL格式及条件,使用(%s,%s)
        @param param: 要更新的  值 tuple/list
        @return: count 受影响的行数
        """
        return self.__query(sql,param)

    def delete(self,sql,param=None):
        """
        @summary: 删除数据表记录
        @param sql: SQL格式及条件,使用(%s,%s)
        @param param: 要删除的条件 值 tuple/list
        @return: count 受影响的行数
        """
        return self.__query(sql,param)

    def begin(self):
        """
        @summary: 开启事务
        """
        self._conn.autocommit(0)

    def end(self,option='commit'):
        """
        @summary: 结束事务
        """
        if option=='commit':
            self._conn.commit()
        else:
            self._conn.rollback()

    def dispose(self,isEnd=1):
        """
        @summary: 释放连接池资源
        """
        if isEnd==1:
            self.end('commit')
        else:
            self.end('rollback');
        self._cursor.close()
        self._conn.close()

参考:https://my.oschina.net/zhouguanghu/blog/32422

#-*- coding: utf-8 -*-  
""" 
数据库管理类 
"""  
import MySQLdb  
from DBUtils.PooledDB import PooledDB  
#自定义的配置文件,主要包含DB的一些基本配置  
from config import configs  

#数据库实例化类  
class DbManager(Singleton):  

    def __init__(self):  
        connKwargs = {'host':configs['DB_HOST'], 'user':configs['DB_USER'], 'passwd':configs['DB_PASS'], 'db':configs['DB_NAME'], 'charset':"utf8"}  
        self._pool = PooledDB(MySQLdb, mincached=0, maxcached=10, maxshared=10, maxusage=10000, **connKwargs)  

    def getConn(self):  
        return self._pool.connection()  

_dbManager = DbManager()  

def getConn():  
    """ 获取数据库连接 """  
    return _dbManager.getConn()  

def executeAndGetId(sql, param=None):  
    """ 执行插入语句并获取自增id """  
    conn = getConn()  
    cursor = conn.cursor()  
    if param == None:  
        cursor.execute(sql)  
    else:  
        cursor.execute(sql, param)  
    id = cursor.lastrowid  
    cursor.close()  
    conn.close()  

    return id  

def execute(sql, param=None):  
    """ 执行sql语句 """  
    conn = getConn()  
    cursor = conn.cursor()  
    if param == None:  
        rowcount = cursor.execute(sql)  
    else:  
        rowcount = cursor.execute(sql, param)  
    cursor.close()  
    conn.close()  

    return rowcount  

def queryOne(sql):  
    """ 获取一条信息 """  
    conn = getConn()  
    cursor = conn.cursor(cursorclass=MySQLdb.cursors.DictCursor)  
    rowcount = cursor.execute(sql)  
    if rowcount > 0:  
        res = cursor.fetchone()  
    else:  
        res = None  
    cursor.close()  
    conn.close()  

    return res  

def queryAll(sql):  
    """ 获取所有信息 """  
    conn = getConn()  
    cursor = conn.cursor(cursorclass=MySQLdb.cursors.DictCursor)  
    rowcount = cursor.execute(sql)  
    if rowcount > 0:  
        res = cursor.fetchall()  
    else:  
        res = None  
    cursor.close()  
    conn.close()  

    return res  

if __name__ == "__main__":  
    res = execute('select count(*) from myt_link_list')  
    print str(res)  

    res = queryOne('select * from myt_link_list limit 20000, 1')  
    print str(res)  

    res = queryAll('select * from myt_link_list limit 10')  
    print str(res)  

参考:http://blog.csdn.net/amandaxy/article/details/7327981
其它实例:http://www.2cto.com/database/201605/506745.html

5 探索多线程使用同一个数据库connection的后果

总结:在多线程的环境中,在不对connection做线程安全处理的情况下,使用单个connection会引起事务的混乱….影响jdbc事务的使用。。。
原文:http://www.knowsky.com/621937.html
https://www.dozer.cc/2016/07/mysql-connection-pool-in-python.html?utm_source=tuicool&utm_medium=referral
参考:
1 指南:http://blog.csdn.net/zhaihaifei/article/details/53996866
2 官方指南:https://cito.github.io/w4py-olde-docs/Webware/DBUtils/Docs/UsersGuide.html

  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值