Python自制简易MySQL连接池实现指南
本文详细介绍如何使用Python语言从零实现一个简易的MySQL连接池。通过解析高并发场景下的资源管理痛点,阐述连接池原理,并提供基于pymysql和configparser的完整代码示例。涵盖配置文件读取、线程锁管理、连接复用及释放逻辑,帮助开发者提升服务器性能并有效管理数据库资源。
本文将介绍一种不同的技术实践,即使用Python语言手动编写一个MySQL连接池。
连接池的基本概念
连接池是一种用于创建和管理数据库连接缓冲区的机制,这些预创建的连接可供任何需要的线程随时调用。在高并发场景下,使用连接池通常比每次请求直接建立新连接具有更优的性能表现,这不仅能显著提升系统响应速度,还能更有效地管理和保护宝贵的服务器资源。
为何需要连接池?
在探讨这一需求之前,我们需要先分析导致服务器在高并发下出现卡顿的根本原因。
在常规操作中,每当有用户通过终端设备连接服务器时,服务器都需要分配一块内存空间来提供服务。同时,每一个从前端传入的请求都要求在MySQL数据库中建立一条新的连接。然而,如果同时存在的连接数量过多,会导致服务器内存占用过高甚至出现卡顿现象。此时,引入连接池技术对所有连接的状态进行统一管理和调度,实现资源的合理分配与回收,就显得尤为重要。
简而言之,采用连接池技术能够有效降低服务器的运行压力。
连接池的工作原理
连接池的核心配置主要包含两个关键参数:默认连接数(初始连接数)和最大连接数。
- 服务启动阶段,系统会首先创建指定数量的空闲连接,并将它们放入连接池中待命。
- 当用户发起连接请求时,系统首先检查池中是否存在可用的空闲连接。
- 当用户发起连接请求时,连接池会尝试从池中取出一个现有的空闲连接交付给用户使用。
- 如果池中没有任何空闲连接,系统会进一步检查当前所有活跃连接的总数是否已超过设定的最大连接数限制。
- 如果当前池中的空闲连接数量不足(即小于最大连接数限制),系统会动态创建新的连接并交付给用户。
- 如果当前活跃连接数已达到最大连接数上限,请求线程将进入阻塞状态,等待池中释放空闲连接后再进行分配。
- 当用户完成数据库操作并准备释放连接时,系统会检查当前存活的连接总数是否大于初始设定的默认值。
- 如果当前连接数小于或等于默认值,该连接将被重新放回空闲池中,以备后续请求复用。
- 如果当前连接数大于默认值,说明存在多余连接,系统将直接销毁该连接以释放资源,不再将其放入池中。
使用Python语言构建简易MySQL连接池
在此部分,我们需要准备核心文件 ThemisPool.py(连接池实现代码)以及 db.cnf(配置文件),它们的目录结构如下所示:
# 推荐目录格式, ThemisPool.py & db.cnf 只需要在同级目录下即可
[your python project]
|
|
|-- util
|
|-- db.cnf
|
|-- ThemisPool.py
ThemisPool.py 文件内容
# 导入依赖
# mysql连接基本库
import pymysql
# 读取配置文件所需要的库
import configparser
import os
# 线程管理所需要的库
import threading
# 创建配置类用户读取配置文件
class Config(object):
def __init__(self, configFileName='db.cnf'):
file = os.path.join(os.path.dirname(__file__), configFileName)
self.config = configparser.ConfigParser()
self.config.read(file)
def getSections(self):
return self.config.sections()
def getOptions(self, section):
return self.config.options(section)
def getContent(self, section):
result = {}
for option in self.getOptions(section):
value = self.config.get(section, option)
result[option] = int(value) if value.isdigit() else value
return result
# 将连接所需要的参数封装在对象中
# 依次为: 数据库密码、需要连接的库名、主机地址[默认 localhost]、端口号[默认 3306]、初始化连接数[默认 3]、最大连接数[默认 6]
class parameter(object):
def __init__(self, password, database, host="localhost",port="3306" user="root", initsize=3, maxsize=6):
self.host = str(host)
self.port = int(port)
self.user = str(user)
self.password = str(password)
self.database = str(database)
self.maxsize = int(maxsize)
self.initsize = int(initsize)
# 连接池
class ThemisPool(parameter):
def __init__(self, fileName='db.cnf', configName='mysql'):
# 加载配置文件, 配置文件名默认为 'db.cnf', 配置标签默认为 'mysql'
self.config = Config(fileName).getContent(configName)
super(ThemisPool, self).__init__(**self.config)
# 创建队列作为 池
self.pool = queue.Queue(maxsize=self.maxsize)
self.idleSize = self.initsize
# 创建线程锁
self._lock = threading.Lock()
# 初始化连接池
for i in range(self.initsize):
# 创建 初始化连接数 数量的连接放入池中
self.pool.put(self.createConn())
# 启动日志
print('\033[1;32m ThemisPool connect database {database}, login is {user} \033[0m'.format(database=self.database,
user=self.user))
# 生产连接
def createConn(self):
# 使用mysql基本类
# pymysql.connect 参数这里不做解释,具体请查阅官网 https://pypi.org/project/PyMySQL/
return pymysql.connect(host=self.host,
port=self.port,
user=self.user,
password=self.password,
database=self.database,
charset='utf8')
# 获取连接
def getConn(self):
self._lock.acquire()
try:
# 如果池中连接够直接获取
if not self.pool.empty():
self.idleSize -= 1
else:
# 否则重新添加新连接
if self.idleSize < self.maxsize:
self.idleSize += 1
self.pool.put(self.createConn())
finally:
self._lock.release()
return self.pool.get()
# 释放连接
def releaseCon(self, conn=None):
try:
self._lock.acquire()
# 如果池中大于初始值就将多余关闭,否则重新放入池中
if self.pool.qsize() < self.initsize:
self.pool.put(conn)
self.idleSize += 1
else:
try:
# 取出多余连接并关闭
surplus = self.pool.get()
surplus.close()
del surplus
self.idleSize -= 1
except pymysql.ProgrammingError as e:
raise e
finally:
self._lock.release()
# 拉取数据(查询)
# 可用语句类型 (select)
def fetchone(self, sql):
themis = None
cursor = None
try:
themis = self.getConn()
cursor = themis.cursor()
cursor.execute(sql)
return cursor.fetchall()
except pymysql.ProgrammingError as e:
raise e
except pymysql.OperationalError as e:
raise e
except pymysql.Error as e:
raise e
finally:
cursor.close()
self.releaseCon(themis)
# 更新
# 可用语句类型 (insert, update, delete)
def update(self, sql):
themis = None
cursor = None
try:
themis = self.getConn()
cursor = themis.cursor()
cursor.execute(sql)
return cursor.lastrowid
except pymysql.ProgrammingError as e:
raise e
except pymysql.OperationalError as e:
raise e
except pymysql.Error as e:
raise e
finally:
themis.commit()
cursor.close()
self.releaseCon(themis)
# 释放连接池本身
def __del__(self):
try:
while True:
conn = self.pool.get_nowait()
if conn:
conn.close()
except queue.Empty:
pass
db.cnf 配置文件内容
[mysql] host = localhost user = root password = 12345678 database = practice initsize = 3 maxsize = 6
所有配置参数的详细说明
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| host | 主机地址 | str | localhost |
| port | 端口号 | int | 3306 |
| user | mysql登录用户名 | str | root |
| password | mysql登录密码 | str | - |
| database | 访问库名 | str | - |
| initsize | 初始化连接数 | int | 3 |
| maxsize | 最大连接数 | int | 6 |
开始使用连接池
from util.ThemisPool import ThemisPool # 初始化ThemisPool连接池 (Initialize the ThemisPool connection pool) db = ThemisPool() # 查询拉取数据,函数会直接返回数据 (Query pull data.It returns data directly) selectSql = "select * from user;" data = db.fetchone(selectSql) # 增、删、改语句, 如果有使用mysql自增长插入的值函数会返回自增长的数据 (insert,upate delete and alter. If there is a value function inserted using mysql self-growth, it will return self-growth data) insertSql = "insert into user values(null,'user001','123456')" id = db.update(selectSql)
自定义配置文件名及配置标签
默认情况下,配置文件名设定为 db.cnf,配置标签(Section)默认为 [mysql]。
例如,你可以自定义配置文件名为 myDB.cnf,并将配置标签设置为 [mysqlConfig]。
# myDB.cnf [mysqlConfig] host = localhost user = root password = 12345678 database = practice initsize = 3 maxsize = 6
# 使用时 ... db = ThemisPool(fileName='myDB.cnf', configName='mysqlConfig') ...
命名思路解析
项目命名为 Themis(忒弥斯),灵感来源于古希腊神话中的秩序女神。这象征着连接池的作用:像秩序女神一样管理所有用户的数据库连接,减少不必要的资源损耗和混乱。
GitHub仓库地址
ThemisPool连接池项目源码
以上就是本次分享的全部内容。在下一个版本中,我们将致力于解决Python中 datetime 类型数据无法直接进行JSON格式化的问题,并将其集成到连接池中。
到此,这篇关于Python自制简易MySQL连接池的实现示例文章介绍完毕。如需了解更多Python与MySQL连接池相关的内容,欢迎搜索本站历史文章或继续浏览后续教程,希望大家今后多多支持本站!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









