基于 pymysql 和 DBUtils 实现mysql连接池
备个份,用在celery 定时任务中,未使用orm框架的情况下,使用原生sql去操作mysql 的方案
pymysql==1.1.1 DBUtils==1.3
import pymysql
from DBUtils.PooledDB import PooledDB
from conf import setting
class MysqlPool(object):
def __init__(self, host='localhost', port=3306, user='root', password='', db='test', charset='utf8mb4', cursorclass=pymysql.cursors.Cursor):
self.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=host,
port=port,
user=user,
password=password,
database=db,
charset=charset,
cursorclass=cursorclass # 添加 DictCursor 类型的游标
)
def get_conn(self):
conn = self.pool.connection()
cursor = conn.cursor()
return conn, cursor
def close_conn(self, conn, cursor):
cursor.close()
conn.close()
class MysqlClint(object):
def __init__(self, cursorclass=pymysql.cursors.Cursor):
self.cursorclass = cursorclass
self.mysql_pool = MysqlPool(
host=setting.MYSQL_HOST,
port=int(setting.MYSQL_PORT),
user=setting.MYSQL_USER,
password=setting.MYSQL_PASSWD,
db=setting.MYSQL_DATABASE,
charset=setting.MYSQL_CHARSET,
cursorclass=cursorclass
)
def execute(self, sql, params=None):
# 每次执行的时候去连接池拿链接,用完即释放
conn, cursor = self.mysql_pool.get_conn()
try:
cursor.execute(sql, params)
# 如果是UPDATE或DELETE操作,需要提交事务
if not sql.lower().startswith('select'):
conn.commit()
return True
# 只有在SELECT操作时,才获取并返回数据
if sql.lower().startswith('select'):
result = cursor.fetchall()
return result
except Exception as e:
print(f"Error occurred: {e}")
# 在出现错误时回滚事务
conn.rollback()
return False
finally:
self.mysql_pool.close_conn(conn, cursor)
def batch_execute(self, sql: str, data: list, batch_size: int = 100):
"""
批量执行
sql sql模版
data 参数
batch_size 一次commit 提交的数量
executemany 也能实现批量更新,但是更多是用来执行insert
"""
# 初始化一个空列表来存储每个批次的SQL语句
batches = []
current_batch = []
# 将更新分组到批次中
for item in data:
current_batch.append(tuple(item))
if len(current_batch) == batch_size:
# 当批次达到指定大小时,将其添加到批次列表中,并重置当前批次
batches.append(current_batch)
current_batch = []
# 不要忘记添加最后一个批次(如果它不为空)
if current_batch:
batches.append(current_batch)
# 执行每个批次的更新
for batch in batches:
conn, cursor = self.mysql_pool.get_conn()
try:
# 逐个执行批次中的更新
for item in batch:
cursor.execute(sql, item)
# 提交当前批次的更改
conn.commit()
except Exception as e:
print(f"Error occurred: {e}")
conn.rollback()
finally:
self.mysql_pool.close_conn(conn, cursor)
def executemany(self, sql, params=None):
# 每次执行的时候去连接池拿链接,用完即释放
conn, cursor = self.mysql_pool.get_conn()
try:
cursor.executemany(sql, params)
conn.commit()
return True
except Exception as e:
print(f"Error occurred: {e}")
# 在出现错误时回滚事务
conn.rollback()
return False
finally:
self.mysql_pool.close_conn(conn, cursor)
# 不是特别必要,并且有死锁风险和资源泄露可能,所以暂时注释掉
"""
不确定性:__del__ 方法的调用时机是不确定的,取决于Python的垃圾回收机制。这意味着资源可能不会立即释放,甚至可能在程序结束前都不会释放。
死锁风险:如果资源的关闭需要与另一个线程同步,__del__ 方法可能在另一个线程持有相关锁时被调用,从而导致死锁。
资源泄露:如果程序异常终止,垃圾回收可能不会发生,导致资源永远不被释放。
"""
# def __del__(self):
# self.mysql_pool.close_conn(self.conn, self.cursor)
mysql_client = MysqlClint() # 先创建好连接池。然后直接使用 pymysql.cursors.DictCursor 性能没有 Cursor 好
mysql_dict_client = MysqlClint(cursorclass=pymysql.cursors.DictCursor)