DBUtil内部实现过程解读

python数据库连接工具DBUtils

模块

DBUtils套件包含两个模块子集,一个适用于兼容DB-API 2接口的模块,一个适用于PyGreSQL的模块。

  • Universal DB-API 2 variant
    图片描述

    该子集下的模块依赖关系如图:
    图片描述

  • Classic PyGreSQL variant
    图片描述

    该子集下的模块依赖关系如图:
    图片描述

SimplePooledDB

DBUtils.SimplePooledDB是池化数据库连接中非常基础的一种实现。相较于PooledDB,它并不那么复杂,且缺少failover机制。SimplePooledDB应视为一种概念演示,不要直接在生产环境使用。

SteadyDB

DBUtils.SteadyDB基于兼容DB-API 2接口的数据库模块创建的普通连接,实现了"加强"连接。具体指当数据库连接关闭、丢失或使用频率超出限制时,将自动重新获取连接。

典型的应用场景如下:在某个维持了某些数据库连接的程序运行时重启了数据库,或在某个防火墙隔离的网络中访问远程数据库时重启了防火墙。

PersistentDB

DBUtils.PersistentDB实现了稳定,线程仿射(thread-affine),持久化的数据库连接。下图显式了使用PersistentDB进行连接时涉及的连接层:
图片描述

某个线程第一次开启一个数据库连接时,该连接将用于此特定线程。即使在线程中关闭连接,连接也会保持打开状态,以便同一个线程的下一次连接请求直接使用。线程结束时该连接会自动关闭。

简而言之:PersistentDB会回收数据库连接从而在整体上增加多线程应用的数据库访问性能,它确保线程之间永远不会共享连接。

因此即使底层的DB-API模块不是connection级别的线程安全,PersistentDB也可以完美实现线程安全,避免在其他线程更改数据库会话或执行跨多个SQL指令的事务时出现问题。


要使用PersistentDB模块,首先传递以下参数创建PersistentDB实例:

  • creator:兼容DB-API 2的数据库模块或返回DB-API 2连接的任意函数
  • maxusage:单个连接的最大重用次数(0或None表示无重用次数限制),达到该限制后自动关闭并重新打开连接
  • setsession:设置连接会话的sql指令列表,比如["set wait_timeout = 100", ...]
  • failures:异常类或异常类元组。在默认的(OperationalError, InternalError)不能处理连接failover机制时使用
  • ping:如果ping()方法可用,该值表示何时使用ping()方法检查连接(0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always, and all other bit combinations of these values)
  • closeable:如果设置为True,将允许手动close()连接,默认为False,忽略关闭连接的操作,只在线程终止时自动关闭
  • threadlocal:表示thread-local数据的类。设置值为threading.local可能获取连接的速度更快,但不一定适用于所有情况(例如,mod_wsgi会清空requests之间的threading.local数据)
  • 传递给creator参数值创建connection对象的参数,如host, database等
import pymysql
from DBUtils.PersistentDB import PersistentDB

persist = PersistentDB(creator=pymysql, user="root", passwd="123456", db="test")
# conn的使用和常规DB-API 2接口类似
conn = persist.connection()

NOTE:需要在连接上调用begin()方法明确开启事务。这可以确保a.只在事务完成时才重新打开连接b.连接被同一个线程重用时回滚。

PooledDB

DBUtils.PooledDB实现了稳定、线程安全的缓存连接池。下图显式了使用PooledDB进行连接时涉及的连接层:
图片描述

使用正整数的maxshared参数和connection级别的线程安全的creator参数创建连接池时,连接池中的连接默认是线程间共享的。但仍可以请求非线程共享的专用数据库连接。

除了共享连接池外,还可以创建至少mincached个,至多maxcached个连接的空闲连接池,在共享连接池未满(不太理解)或线程请求专用数据库连接时使用。当某个线程关闭不再共享的连接时,该连接将回收到空闲连接池以便再次使用。

如果底层的DB-API 2模块非线程安全,将使用线程锁确保PooledDB连接是线程安全的。但对于线程专用的连接,要小心更改数据库会话或执行跨多个SQL指令的事务带来的不良影响。


要使用PoolDB模块,首先传递以下参数创建PoolDB实例:

  • creator:同PersistentDB
  • mincached:连接池中空闲连接的初始数量(0表示不创建初始空闲连接)
  • maxcached:连接池中允许的最大空闲连接数(0或None表示无限制)
  • maxshared:允许的最大共享连接数(0或None表示所有连接都是专用的),When this maximum number is reached, connections are shared if they have been requested as shareable
  • maxconnections:允许的最大连接数(0或None表示无限制)
  • blocking:查过最大值是否阻塞。True表示将阻塞直到释放新的连接,默认False表示抛出异常
  • maxusage:同PersistentDB
  • setsession:同PersistentDB
  • reset:返回连接池时应该怎样重置连接(False或None将只回滚明确调用了begin()开启的事务,默认值为True,出于安全考虑总是会回滚)
  • failures:同PersistentDB
  • ping:同PersistentDB
  • 传递给creator参数值创建connection对象的参数,如host, database等
import pymysql
from DBUtils.PooledDB import PooledDB

pool = PooledDB(creator=pymysql, 5, user="root", passwd="123456", db="test")
# conn的使用和常规DB-API 2接口类似
conn = pool.connection()

对于线程共享的连接池,可以用以下方式获取线程专用连接:

conn = pool.connection(shareable=False)
# 或者
conn = pool.dedicated_connection()

对于不再使用的连接,调用close()方法回收到连接池。

在多线程环境中,不要写以下代码,这会导致连接过早释放并被其他线程重用,如果连接非线程安全可能导致程序出现严重错误:

pool.connection().cursor().execute(...)

NOTE:需要在连接上调用begin()方法明确开启事务。这可以确保a.只在事务完成时才重新打开连接b.连接在返回连接池之前执行回滚c.连接不会被其他线程共享

如何选择

PooledDB和PersistentDB都通过回收数据库连接,且即使数据库连接中断也能保持稳定性的方式从而达到提升数据库访问性能的目的。在现实场景中应该如何选择呢?对于保持常量线程数且频繁使用数据库的应用,使用PersistentDB;对于频繁开启、结束线程的应用,使用PooledDB。

其他

如果程序中使用了ORM框架,如SQLObjectSQLAlchemy,不需要使用DBUtils,因为这些框架自身维护了连接池。

数据库线程安全级别:
图片描述
比如pymysql就是可以共享模块但不能共享连接,查看方式pymysql.threadsafety

https://segmentfault.com/a/1190000017952033

posted @ 2014-05-23 22:04  南哥的天下  阅读(470)  评论(0编辑  收藏  举报