后文以django 连接mysql 为例,进行说明。
背景
django 的默认机制中,在查询时,会建立数据库连接,查询结束后,会断开数据库连接 —— 没有连接池的概念。
官方文档中描述,可以通过设置 CONN_MAX_AGE 让执行数据库查询后,不立即断开连接 —— 一定时间内保持连接,当新的查询任务在连接未断开时,可以复用此连接。这样的好处有:
避免频繁的建立连接,断开连接,消耗数据库的资源
避免大并发时,数据库连接数过多
目标:限制连接数
连接池的引入,可以在并发数高时,限制程序和数据库的连接数,避免数据库连接数太大的错误。
当django 面临高并发时,就会引发连接数过多的错误,此时我们可能会希望使用连接池(允许单个接口增加等待资源,以执行查询的时间)。
比如(特定场景):甲方给乙方提供了user_test 的账号,用于查询db_test数据库,但是限制了user_test 在 db_test 上的连接数。
此时,django 没有连接池的概念就对不上了:因为就算设置 CONN_MAX_AGE,也无法有效限制数据库连接数。
解决方案
限制线程数
方法
在通过uwsgi 部署时,可以限制单个进程中线程数,通过限制整个应用的并发量来限制对于数据库的连接。
优点
操作简单,实现方便
缺点
并发能力大大降低。因为部分请求是不会实际发生数据库操作的,对于连接数的控制不够细腻。
第三方连接引擎
方法
在settings.py 文件中,需要指定数据库驱动。例如默认的django.db.backends.mysql。
可以通过引入第三方的驱动,或者自己完成的驱动,来完成连接池的效果。
优点
对连接数量的控制细腻,同时使用简单(自己完成不简单)
缺点
难以把控第三方数据库驱动稳定性和正确性。以现在各方的资料博客显示,各个第三方库的维护大概就只有2-3次,当在企业级项目开发时,不敢使用(特别是django 3.0 开始对接 python 异步特性)
权限限制(推荐)
方法
在进程启动时,初始化一个线程安全的队列,放入所有令牌。通过对令牌的获取,实现执行权限的限制,限制实际的操作并发数,来限制数据库连接数,同时保持了django 中提供的,CONN_MAX_AGE 断开了解的功能。
优点
控制细腻,操作简单,不需要完成复杂的第三方数据库驱动
缺点
方法属于用时限制,不能像第三方连接引擎一样,在底层实现限制,如果不能把握好对django orm 的执行过程(懒加载的详细),那么会造成无法有效限制数据库连接数。
代码尝试
针对第三种实现方式,队列限制的方式:
queue = Queue()
for i in range(10):
queue.put(i)
def limit(func):
def wrapper(*args,**kwargs):
i = queue.get()
result = None
message = None
try:
result = func(*args,**kwargs)
except Exception as e:
message = e
logging.error(e)
finally:
queue.task_done()
queue.put(i)
if message is not None:
raise Exception(message)
else:
return result
return wrapper
@limit
def test_do(a):
value = Teachers.objects.all()
return value
class Test(View):
def get(self,requests):
value = test_do("test")
return JsonResponse({"key":value})
为何不引入数据库连接池?
django 作为一个大而全的框架,为什么没有使用连接池技术呢?
开发设计方,不会没想到我这个较低层面所看到的问题,那么未使用连接池,一定有他的原因,那么接下来可以查找两个线路,来回答:
有什么办法可以简单有效的限制数据库连接数?
查询过程中,找到的资料一直不够满意(不够简单),思考很久,找到了自己暂时满意的答案。
此处,不使用连接池有什么好处?
未使用连接池,是对数据库不太负责的方式,但是带来的好处是,可以应对较高的并发,把并发的压力主要交给数据库。而数据库的压力,可以通过读写分离,分库分表,来缓解 —— 这比尝试解决python 速度慢的问题,简单了不少,只是操作并不简单。
所以,一方面在使用时,想限制连接数,操作简单(还满足MVC ,把所有的数据库操作封装出来),而不限制连接数时,压力适当交给数据库,减少python 速度慢带来的影响。




