Python可以使用MySQLdb进行数据库操作,但是每次连接MySQL数据库请求时,都是独立的去请求访问,相当浪费资源,而且访问数量达到一定数量时,对mysql的性能会产生较大的影响。实际使用中,通常会使用数据库的连接池来访问数据库达到资源复用的目的。这里介绍一下在django中如何实现数据库连接池。

DBUtils

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

下载地址:DBUtils

DBUtils提供两种外部接口:

  1. PersistentDB :提供线程专用的数据库连接,并自动管理连接。
  2. PooledDB :提供线程间可共享的数据库连接,并自动管理连接。

将下载下来的源码解压,放在应用程序根目录”database”中,修改SteadyDB.py文件,在类 SteadyDBConnection 中添加3个成员方法:

1
2
3
4
5
6
7
8
9
def autocommit(self, *args, **kwargs):
self._con.autocommit(*args, **kwargs)

def get_server_info(self):
return self._con.get_server_info()

@property
def encoders(self):
return self._con.encoders

修改 PooledDB.py 和 PersistentDB.py 两个文件,将

1
from DBUtils.SteadyDB import connect

改为:

1
from .SteadyDB import connect

将Django中的mysql模块加入应用程序目录中,将backends目录下的 __init__.py文件和mysql目录复制到database目录下的db目录中,目录结构如下:

1
2
3
4
5
6
7
8
9
应用程序根目录
|
| -- database
|
| -- DBUtils
| -- db
|
| -- __init__.py
| -- mysql

在mysql目录下添加pool.py文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# coding:utf-8
'''A connection pool of MySQL in Django 1.5 based on DBUtils.'''
import MySQLdb
from database.DBUtils.PooledDB import PooledDB

class ConnectionWrapper(object):
def __init__(self, connection):
self._conn = connection

def __getattr__(self, method):
''' 代理数据库连接的属性方法 '''
return getattr(self._conn, method)

def close(self):
''' 代理Django的关闭数据库连接 '''
self._conn.close()

class DBWrapper(object):
def __init__(self, module):
self._connection = None
self._db = module
self._pool = {}

def __getattr__(self, item):
return getattr(self._db, item)

def _clear_connections(self, **kwargs):
''' 关闭已有连接 '''
conn = MySQLdb.connect(**kwargs)
cursor = conn.cursor()
sql = ''
cursor.execute('show full processlist;')
processlist = cursor.fetchall()
for th in processlist:
if th[3] == kwargs.get('db') and th[0] != conn.thread_id():
sql += 'kill %s;' % th[0]
if len(sql.split(';')) > 1:
cursor.execute(sql)
cursor.close()
conn.close()

def connect(self, *args, **kwargs):
''' 创建连接 '''
db = kwargs.get('db')
if db not in self._pool:
size = kwargs.get('size')
if 'size' in kwargs:
kwargs.pop('size')
self._clear_connections(**kwargs)
self._pool[db] = PooledDB(self._db, mincached=size, maxcached=size, *args, **kwargs)
self._connection = self._pool[db].connection()
return ConnectionWrapper(self._connection)

修改mysql目录下的 base.py 文件,在

1
import MySQLdb as Database

下添加两行

1
2
from .pool import DBWrapper
Database = DBWrapper(Database)

修改类 DatabaseWrapper 中的方法 get_connection_params,在

1
2
if settings_dict['PORT']:
kwargs['port'] = int(settings_dict['PORT'])

下添加如下代码,使连接池支持”SIZE”参数

1
2
if settings_dict.get('SIZE'):
kwargs['size'] = int(settings_dict['SIZE'])

在settings.py配置文件的数据库项中,可以通过配置”SIZE”参数来指定连接池中某数据库的连接数量,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.mysql',
'ENGINE': 'database.db.mysql',
'NAME': 'cartoon',
'USER': 'root',
'PASSWORD': 'root',
'HOST': '',
'PORT': '',
# 指定10个连接到cartoon库
'SIZE': '10', # Default '0' means unlimit connection pool size
'OPTIONS': {
'init_command': 'SET default_storage_engine=INNODB',
},
}
}

至此,连接池改造完成。

运行程序,进入mysql命令行,执行

1
show full processlist;

可以看到连接池里面所有的连接。
[^1]: 参考链接: Django实现MySQL连接池.