Flask数据库连接池
一、问题引入
-
安装模块
-
pip install pymysql
-
-
简单示例
-
import pymysql # 连接数据库 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='test') # 创建游标对象,返回字典类型 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 使用参数化查询执行SQL语句,传入参数使用字典形式 cursor.execute("select * from users") # 获取查询结果中的下一行,返回字典形式 obj = cursor.fetchone() # 提交事务 conn.commit() # 关闭游标 cursor.close() # 关闭数据库连接 conn.close() # 打印查询结果 print(obj)
-
-
在 Flask 应用中,如果直接在全局范围内创建数据库连接(
conn
)和游标(cursor
),会导致以下问题:- 当应用运行在多进程模式下时,多个进程会同时操作同一个数据库连接,可能会导致数据库操作错乱。
- 每次请求都应该使用独立的数据库连接,以保证数据库操作的隔离性和安全性。
二、优化方案
为了解决上述问题,可以采取以下优化措施:
- 在每个请求的视图函数中独立创建和关闭数据库连接。
- 使用数据库连接池来管理数据库连接,提高资源利用率和应用性能。
1. 独立创建和关闭数据库连接
在每个视图函数中,创建数据库连接和游标,执行完数据库操作后关闭它们:
@app.route('/', methods=['GET', 'POST'])
def index():
# 创建数据库连接和游标
conn = pymysql.connect(...) # 省略了连接参数
cursor = conn.cursor(cursor=DictCursor)
try:
cursor.execute("SELECT * FROM test WHERE ID < 10")
res = cursor.fetchall()
print(res)
finally:
# 确保游标和连接被关闭
cursor.close()
conn.close()
return 'index'
2. 使用数据库连接池
使用 dbutils
库的 PooledDB
创建一个数据库连接池,并通过单例模式管理它:
安装模块:pip install dbutils
设置数据库连接池(settings.py
):
# pip install dbutils import pymysql from dbutils.pooled_db import PooledDB class Config(object): DEBUG = True # 其他配置.. # 创建数据库连接池 PYMYSQL_POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='123456', database='test', charset='utf8', cursorclass=pymysql.cursors.DictCursor )
使用连接池:
from flask import Flask
from settings import Config
app = Flask(__name__)
app.config.from_object(Config)
@app.route('/')
def index():
conn = Config.PYMYSQL_POOL.connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM user")
users = cursor.fetchall()
print(users)
return "Hello World!"
三、测试连接池
1.使用数据库连接池
优点:
- 减少了数据库连接的开销,提高了性能。
- 限制了同时打开的连接数,有助于防止数据库过载。
示例:
@app.route('/pool')
def pool():
conn = Config.PYMYSQL_POOL.connection() # 从池中获取连接
# ... 执行数据库操作 ...
conn.close() # 返回连接到池中,而不是关闭它
return jsonify(res)
2.不使用数据库连接池
缺点:
- 每次请求都创建和关闭连接,增加了数据库的开销。
- 如果并发请求很多,可能导致数据库连接数过多,甚至引发错误。
示例:
@app.route('/no_pool')
def no_pool():
conn = pymysql.connect(...) # 手动创建连接
# ... 执行数据库操作 ...
conn.close() # 关闭连接
return jsonify(res)
3.测试结果
-
测试脚本
-
import requests from threading import Thread # 没有连接池 def task(): # res = requests.get('http://127.0.0.1:5000/no_pool') # 没有池 res = requests.get('http://127.0.0.1:5000/pool') # 有池 print(res.json()) if __name__ == '__main__': l = [] for i in range(100): t = Thread(target=task) t.start() l.append(t) for i in l: i.join()
-
-
查看数据库连接数
SHOW STATUS LIKE '%Threads%';
- 测试结果
- 使用连接池:连接数明显较少,因为连接被重用。
- 不使用连接池:连接数很大,因为每个线程都创建了一个新的连接。
- 使用连接池的测试明显比不使用连接池的测试慢?
- 池大小限制:如果池中的连接数不足以满足并发请求的数量,线程需要等待可用连接,这会增加响应时间。
- 连接创建时间:如果连接池中的连接创建时间较长,或者池中的连接被频繁创建和销毁,也会导致性能下降。