pymysql链接池、事物的使用浅谈
关于dbutils模块的说明
代码示例中导入的方式是:
from DBUtils.PooledDB import PooledDB
使用这种方法导入需要指定1.3版本install:
pip3 install DBUtils==1.3
如果DBUtils的版本高于1版本需要按照这种方式引入:
from dbutils.pooled_db import PooledDB
准备工作
MySQL库表
在t1库创建一张名为test_table的表,里面的数据如下:
配置文件
跟项目脚本同级目录存放MySQL的配置settings.ini:
[MYSQL] HOST = 127.0.0.1 PORT = 3306 USER = root PASSWORD = 123 DATABASE = t1 CHARSET = utf8
pymysql的基本操作
# -*- coding:utf-8 -*- import os import pymysql import configparser # 获取配置 current_path = os.path.abspath(".") config = configparser.ConfigParser() config.read(os.path.join(current_path,"settings.ini")) mysql_conf = dict( host=config["MYSQL"]["HOST"], port=int(config["MYSQL"]["PORT"]), # 注意这里得把port转换成int! user=config["MYSQL"]["USER"], password=config["MYSQL"]["PASSWORD"], database=config["MYSQL"]["DATABASE"], charset=config["MYSQL"]["CHARSET"], ) # 连接数据库 —— 注意这里password得写成字符串类型 conn = pymysql.connect(**mysql_conf) # 获取光标对象 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) ## 执行sql语句 sql_str1 = """ select * from test_table """ # 获得受影响的信息条数 res_count = cursor.execute(sql_str1) print(res_count) # 3 # 获取数据 res_datas = cursor.fetchall() print(res_datas) # [{'id': 1, 'name': 'whw', 'age': 18}, {'id': 2, 'name': 'naruto', 'age': 19}, {'id': 3, 'name': 'sasuke', 'age': 20}] ## 关闭链接 cursor.close() conn.close()
将连接数据库封装为一个方法,每次需要连接的时候调用该方法获取conn和cursor对象,但是这样会有损耗,因为每次都需要 建立连接->执行数据库操作->释放连接。而数据库连接池为维护一个保存有多个数据库连接的池子,每次需要连接数据库时,从连接池中取出一个连接进行使用即可,使用完毕后连接不会释放,而是归还给连接池进行管理,节省了不断建立连接和释放连接的过程。
使用DBUtils+pymysql建立数据库连接池
这里还使用了with上下文管理与装饰器两种方式实现了链接池的效果。
另外还使用pymysql实现了事物的操作。
# -*- coding:utf-8 -*- import os import pymysql import configparser from DBUtils.PooledDB import PooledDB # 获取配置 current_path = os.path.abspath(".") config = configparser.ConfigParser() config.read(os.path.join(current_path,"settings.ini")) mysql_conf = dict( host=config["MYSQL"]["HOST"], port=int(config["MYSQL"]["PORT"]), # 注意这里得把port转换成int! user=config["MYSQL"]["USER"], password=config["MYSQL"]["PASSWORD"], database=config["MYSQL"]["DATABASE"], charset=config["MYSQL"]["CHARSET"], ) class MySQLPool(object): # 类属性 pool = PooledDB(creator=pymysql,**mysql_conf) # 注意creator参数 def __enter__(self): self.conn = MySQLPool.pool.connection() self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor) return self def __exit__(self, exc_type, exc_val, exc_tb): # 关闭链接 self.cursor.close() self.conn.close() ## with上下文使用方法 def func1(): with MySQLPool() as db: sql_str1 = """ select * from test_table """ res_count = db.cursor.execute(sql_str1) print(res_count) res_datas = db.cursor.fetchall() print(res_datas) # 添加一条数据 sql_insert = """insert into test_table(name,age) values(%s,%s)""" res = db.cursor.execute(sql_insert,["wanghw11",18]) print(res) db.conn.commit() # func1 func1() ## 装饰器方式执行 def mysql_wrapper(func): def wrapper(*args,**kwargs): with MySQLPool() as db: result = func(db,*args,**kwargs) # return return result return wrapper @mysql_wrapper def func2(db,*args,**kwargs): # 执行事物 try: # 添加一条数据 sql_insert = """insert into test_table(name,age) values(%s,%s)""" # 修改一条数据 sql_update = """ update test_table set age=22 where name='whw' """ db.cursor.execute(sql_insert,["whw666",20]) db.cursor.execute(sql_update) except Exception as e: # 回滚 db.conn.rollback() print(f"事物执行失败:{e}") else: # 提交事物 db.conn.commit() print("事物执行成功:",db.cursor.rowcount) # func2 func2()
可以看到,通过DBUtils,实例化一个PooledDB对象作为MysqlPool类的类属性,通过重写__enter__和__exit__方法让我们在进入with语句时从连接池中获取到数据库连接,在with语句结束后,自动释放连接池,归还给连接池。
成功执行事物后可以在数据库中查看一下数据的变化。
模拟一下事物执行失败的情形
这里使用自定义的异常模拟事物执行失败时的现象,数据库中的数据都没有任何变化。
# -*- coding:utf-8 -*- import os import pymysql import configparser from DBUtils.PooledDB import PooledDB # 获取配置 current_path = os.path.abspath(".") config = configparser.ConfigParser() config.read(os.path.join(current_path,"settings.ini")) mysql_conf = dict( host=config["MYSQL"]["HOST"], port=int(config["MYSQL"]["PORT"]), # 注意这里得把port转换成int! user=config["MYSQL"]["USER"], password=config["MYSQL"]["PASSWORD"], database=config["MYSQL"]["DATABASE"], charset=config["MYSQL"]["CHARSET"], ) class MySQLPool(object): # 类属性 pool = PooledDB(creator=pymysql,**mysql_conf) # 注意creator参数 def __enter__(self): self.conn = MySQLPool.pool.connection() self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor) return self # 记得return self def __exit__(self, exc_type, exc_val, exc_tb): # 关闭链接 self.cursor.close() self.conn.close() # 自定义异常类 class MyException(BaseException): def __init__(self,msg): super().__init__() self.msg = msg def __str__(self): return self.msg ## 装饰器方式执行 —— 执行事物是出现异常的测试 def mysql_wrapper(func): def wrapper(*args,**kwargs): with MySQLPool() as db: result = func(db,*args,**kwargs) # return return result return wrapper flag = True @mysql_wrapper def func2(db,*args,**kwargs): # 执行事物 try: # 添加一条数据 sql_insert = """insert into test_table(name,age) values(%s,%s)""" # 修改一条数据 sql_update = """ update test_table set age=23 where name='whw' """ db.cursor.execute(sql_insert,["whw123",20]) db.cursor.execute(sql_update) # 这里做一个逻辑判断 —— 可根据具体的业务而定 if flag: raise MyException("执行事物是发生异常!") except MyException as e: # 回滚 db.conn.rollback() print(f"事物执行失败:{e}") except Exception as e: # 回滚 db.conn.rollback() print(f"事物执行失败:{e}") else: # 提交事物 db.conn.commit() print("事物执行成功:",db.cursor.rowcount) # func2 func2()
参考博客
更多pymysql的基本操作参考我的博客