Python实现与MySQL长连接的客户端
下面的代码是使用Python建立的和MySQL长连接的简单客户端示例。
当和MySQL的连接断开后,会自动进行重连(被动式的重连,即只有调用增self.execute()、删self.execute()、改self.execute()、查self.query()方法出现异常的时候,才会触发重连)。可以修改“self.__check_exception_type()”方法,在该方法中完善对应的异常信息,来完善代码。
import datetime
import logging
import time
import traceback
import pymysql
import threading LOG_FORMAT = "%(asctime)s - %(levelname)s [%(filename)s-%(funcName)s] Line: %(lineno)s] - %(message)s"
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
_logger = logging.getLogger() __all__ = ["MySQLClient"] class MySQLClient:
"""
self.__database_status: 用来判断当前的数据库连接是否正常。True代表数据库状态正常
self.__has_reconnect_thread: 用来判断是否已经开启了重连数据库的后台线程。True代表已经有启动线程在进行数据库的重连了 每次对数据库执行增、删、改、查等操作的时候,都要先判断下 self.__database_status 的值。
1、当值为True的时候,代表数据库连接正常,正常执行相关的增、删、改、查等操作
2、当值为False的时候,代表数据库已经是连接异常状态,那么就再检查下 self.__has_reconnect_thread 的值:
2.1、当 self.__has_reconnect_thread 的值是False的时候,说明还没有启动后台重连数据库的线程,那么会启动一个线程
进行数据库重连。后台线程重连成功后,会把 self.__database_status 的值设置成True,
再把 self.__has_reconnect_thread 变量设置为False,并退出该重连数据库的线程。
2.2、当 self.__has_reconnect_thread 的值是True的时候,说明已经有一个后台线程进行数据库的重连,就不再启动新的
重连数据库的线程了。
3、在执行增、删、改、查等操作的时候,如果碰到了数据库连接异常,那么就会把 self.__database_status 的值设置成False
"""
def __init__(self, host, username, password, database="", table_name="", port=3306, logger=None):
"""
:param host:
:param username:
:param password:
:param port:
:param logger: 使用自定义的logger
"""
self.__host = host
self.__username = username
self.__password = password
self.__port = port
self.__database = database
self.__table_name = table_name
self.__logger = logger if logger else _logger self.__database_status = False # 数据库连接状态标识位
self.__has_reconnect_thread = False # 是否已经有重连数据库的后台线程标识位 self.__connection = None # 对数据库的连接
self.__cursor = None # 所有的增、删、改、查都是使用游标即该变量进行的操作 # 实例化对象的时候就进行和数据库的连接,连接成功则可以直接调用对象的query()或者insert()等方法,连接失败则抛出异常
self.connect_to_db() # 开启定时检查数据库状态的线程
self.__thread_check_db_status() def __get_database_status(self):
return self.__database_status def __set_database_status(self, status: bool):
self.__database_status = status def __get_has_reconnect_thread(self):
return self.__has_reconnect_thread def __set_has_reconnect_thread(self, status: bool):
self.__has_reconnect_thread = status def __thread_reconnect_to_db(self, timespan=5):
"""
如果类变量 __has_reconnect_thread 是False,则启动一个线程进行数据库的重连,并且设置 __has_reconnect_thread 变量
为True 如果类变量 __has_reconnect_thread 是True,说明已经有对数据库进行重连的线程了,此时 __has_reconnect_thread 值为True 在对数据库重连的后台线程中,如果重连成功,则设置类变量 __database_status 为True并且退出该线程,同时设置
__has_reconnect_thread 为False。 :param timespan: 两次重连的时间间隔。
:return:
"""
def __thread_connect_to_database():
self.__set_has_reconnect_thread(True) # 设置全局变量,表示已经有线程进行重连数据库了。
while True: # 在while循环中一直进行重连,直到重新连上数据库
if not self.__connect_to_db_once():
self.__logger.error("Re-connect to database failed !!! Will retry after [%s] seconds" % timespan)
time.sleep(timespan)
else:
self.__set_database_status(True) # 设置数据库连接状态为True
self.__set_has_reconnect_thread(False) # 重连线程要退出了,所以设置该状态为False
self.__logger.info("Re-connect to database successfully @_@")
break # 退出循环,即退出重连线程 if not self.__get_has_reconnect_thread():
self.__set_has_reconnect_thread(True)
t = threading.Thread(target=__thread_connect_to_database, name="Thread-reconnect-to-db")
t.start()
# else:
# print("Already has thread connect to database") def __thread_check_db_status(self):
"""
以后台线程的形式,检查数据库连接状态
:return:
"""
def __inner_thread_check_db_status():
while True:
try:
if not self.__get_database_status():
self.__thread_reconnect_to_db()
except Exception:
self.__logger.error(str(traceback.format_exc()))
finally:
time.sleep(1)
t = threading.Thread(target=__inner_thread_check_db_status)
t.setDaemon(True)
t.start() def connect_to_db(self, timeout=60):
"""
调用该方法,进行数据库的连接,如果超过设置的时间还没有连接的话,则抛出异常
:param timeout:
:return: 该类对象(self)或者抛出异常
"""
# 如果数据库已经处于连接状态,则直接返回。因此该方法可以被重复调用执行。
if self.__get_database_status():
return self # 此时还没有连接到数据库,就需要进行对数据库的连接。
start_timestamp = time.time()
while True:
try:
if self.__connect_to_db_once(log=True):
self.__set_database_status(True)
break
else:
end_timestamp = time.time()
if (end_timestamp - start_timestamp) < timeout:
self.__logger.error("Connect to database failed, will re-connect after 1 seconds ...")
self.__set_database_status(False)
time.sleep(1)
else:
break
except Exception:
self.__logger.error("Connect to database exception !!! " + traceback.format_exc())
break # 连接数据库成功,则返回该类对象
if self.__get_database_status():
self.__logger.info("Connect to database successfully @_@")
return self
else: # 连接数据库失败,则返回None
message = "After [%s] seconds, still can not connect to database !!!" % timeout
self.__logger.error(message)
raise Exception(message) def __check_exception_type(self, e, trac):
"""
根据不同的异常,进行分别处理。比如有的是SQL异常,有的是数据库连接异常。
如果是数据库连接异常等,需要设置数据库连接状态,以便进行重连。
:param e: 简短的异常信息
:param trac: traceback.format_exc() 捕获到的详细信息
:return:
""" # 1、SQL语法错误的异常,不属于数据库连接异常,不需要重连
if "pymysql.err.ProgrammingError" in str(trac):
# pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax;
# check the manual that corresponds to your MySQL server version for the right syntax to use near 'order by domain' at line 1")
self.__logger.error("SQL syntax exception !!! " + str(e))
raise Exception(e) from None # 2、SQL语法错误的异常,不属于数据库连接异常,不需要重连
if "pymysql.err.InternalError" in str(trac):
# pymysql.err.InternalError: (1054, "Unknown column 'names' in 'field list'")
self.__logger.error("SQL syntax exception !!! " + str(e))
raise Exception(e) from None # 3、连接不上数据库服务器,属于数据库连接异常,需要重连
if "pymysql.err.OperationalError" in str(trac) and "Can't connect to MySQL server on" in str(trac):
# pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '10.88.65.5' (timed out)")
self.__set_database_status(False) # 设置数据库连接状态为异常状态
self.__logger.error("Connect to MySQL server exception !!! " + str(e))
raise Exception(e) from None # 4、连接断开,属于数据库连接异常,需要重连
if "pymysql.err.OperationalError" in str(trac) and "Lost connection to MySQL server" in str(trac):
# pymysql.err.OperationalError: (2013, 'Lost connection to MySQL server during query ([WinError 10060]
# 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。)')
self.__set_database_status(False) # 设置数据库连接状态为异常状态
self.__logger.error("Lost connection to MySQL server exception !!! " + str(e))
raise Exception(e) from None # 5、连接持续中断,属于数据库连接异常,需要重连
if "pymysql.err.InterfaceError" in str(trac):
# pymysql.err.InterfaceError: (0, '')
self.__set_database_status(False) # 设置数据库连接状态为异常状态
self.__logger.error("Disconnect to MySQL server exception !!! " + str(e))
raise Exception(e) from None # 6、未知即未被统计到的异常,待完善,统一归入这里,需要重连
else:
self.__set_database_status(False) # 设置数据库连接状态为异常状态
self.__logger.error("Unknown exception !!! " + str(trac))
raise Exception(e) from None def __connect_to_db_once(self, log=False) -> bool:
"""
进行一次数据库的连接。
连接成功,则把获取到的连接对象设置到变量 self.__db_client 中,并返回True。同时设置全局变量 self.__connection 和 self.__cursor
连接失败,则返回False
:return:
"""
try:
connection = pymysql.connect(host=self.__host,
port=self.__port,
user=self.__username,
password=self.__password,
database=self.__database,
)
self.__connection = connection
self.__cursor = connection.cursor()
return True
except Exception as e:
try:
# 如果在单次连接的时候,出现异常并经过 self.__check_exception_type()方法处理后,该方法仍然抛出异常,那么说明
# 该次连接失败,在这个单次连接的方法中,捕获下即可
self.__check_exception_type(e, traceback.format_exc())
except Exception:
return False def query(self, sql: str) -> list:
"""
查询部分 :return: 返回列表或者抛出异常(异常是因为客户提供的SQL格式错误)
"""
ret_list = [] # 数据库连接状态异常,则调用 self.__thread_reconnect_to_db() 方法进行重连,在该方法中进行实际判断,是否启动重连的线程。
if not self.__get_database_status():
self.__thread_reconnect_to_db()
return ret_list
try:
self.__cursor.execute(query=sql)
ret_list = list(self.__cursor.fetchall())
return ret_list
except Exception as e:
self.__check_exception_type(e, traceback.format_exc()) def execute(self, sql: str, auto_commit=True) -> bool:
"""
增、删、改部分
:return: 返回布尔值True|False或者抛出异常
"""
ret = False # 数据库连接状态异常,则调用 self.__thread_reconnect_to_db() 方法进行重连,在该方法中进行实际判断,是否启动重连的线程。
if not self.__get_database_status():
self.__thread_reconnect_to_db()
return ret try:
self.__cursor.execute(sql)
if auto_commit:
self.__connection.commit()
return True
except Exception as e:
self.__check_exception_type(e, traceback.format_exc()) def manual_commit(self):
# 数据库连接状态异常,则调用 self.__thread_reconnect_to_db() 方法进行重连,在该方法中进行实际判断,是否启动重连的线程。
if not self.__get_database_status():
self.__thread_reconnect_to_db()
return False
try:
self.__connection.commit()
return True
except Exception as e:
self.__check_exception_type(e, traceback.format_exc()) def test(self):
"""
测试代码部分
:return:
"""
while True:
try:
sql = "SELECT domain, account FROM {tablename} limit 10".format(tablename=self.__table_name)
# sql = "SELECT COUNT(*) FROM {tablename}".format(tablename=self.__table_name)
sql = "update {tablename} set username='JCL10231024' where id='007e7571-c74a-47c1-99d5-a3f868bd7dd7'".format(tablename=self.__table_name)
print(sql)
ret_list = self.execute(sql, auto_commit=False)
self.manual_commit()
print("ret_list: ", ret_list)
except Exception:
pass
finally:
print("Sleep 5 seconds ...")
time.sleep(5) if __name__ == '__main__': obj = MySQLClient(host="localhost",
port=3306,
username="username",
password="password",
database="database",
table_name="table_name"
)
obj.test()
Python实现与MySQL长连接的客户端的更多相关文章
- mysql长连接和短连接的问题 转
什么是长连接? 其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态. 通常的短连接操作步骤是: 连接->数据传输->关闭连接: 而长连接通常就是: 连接-> ...
- mysql长连接和短连接的问题
什么是长连接? 其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态. 通常的短连接操作步骤是: 连接->数据传输->关闭连接: 而长连接通常就是: 连接-> ...
- mysql 长连接断开问题
从MySQL 5.0.3开始,默认情况下禁止再连接,这是5.0.13中的新选项,提供了一种以显式方式设置再连接行为的方法. mysql应用程序建立的长连接,大约过8小时会断开[没测过,网上都是这么说的 ...
- mysql长连接与短连接
什么是长连接? 其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态. 通常的短连接操作步骤是: 连接->数据传输->关闭连接: 而长连接通常就是: 连接-> ...
- mysql长连接
长连接是干嘛的: 它是做连接复用的: 在openresty中的lua-resty-mysql 里 connect方法去连接mysql时会去ngx_lua cosocket连接池中寻找是否有可用连接 ...
- php关于mysql长连接问题
1.当 函数 mysql_connect 的前三个参数(server username password)相同,并且第四个参数(new_link)不传递时候,重复调用 mysql_connect 是会 ...
- Spring Boot+Socket实现与html页面的长连接,客户端给服务器端发消息,服务器给客户端轮询发送消息,附案例源码
功能介绍 客户端给所有在线用户发送消息 客户端给指定在线用户发送消息 服务器给客户端发送消息(轮询方式) 项目搭建 项目结构图 pom.xml <?xml version="1.0&q ...
- PHP和mysql的长连接
关于 PHP MySQL 长连接.连接池的一些探索 PHP连接MySQL的方式,用的多的是mysql扩展.mysqli扩展.pdo_mysql扩展,是官方提供的.php的运行机制是页面执行完会释放所有 ...
- Ajax长连接和SignalR(刷新客户端数据)的区别
ajax实现长连接 <%@ page language="java" import="java.util.*" pageEncoding=" ...
- PHP-数据库长连接mysql_pconnect的细节
PHP的MySQL持久化连接,美好的目标,却拥有糟糕的口碑,往往令人敬而远之.这到底是为啥么.近距离观察后发现,这家伙也不容易啊,要看Apache的脸色,还得听MySQL指挥. 对于作为Apache模 ...
随机推荐
- 【Leetcode】 # 20 有效的括号 Rust Solution About Rust Stack implement
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效.有效字符串需满足: 左括号必须用相同类型的右括号闭合. 左括号必须以正确的顺序闭合.注意空字符 ...
- 尚医通day16-网站怎么接入微信扫码支付?
第01章-准备工作 1.微信支付产品介绍 参考资料:产品中心 - 微信支付商户平台 (qq.com) 付款码支付.JSAPI支付.小程序支付.Native支付.APP支付.刷脸支付 1.1.付款码支付 ...
- 深入了解ApacheZeppelin:如何构建高效的数据科学平台
目录 引言 随着数据科学和人工智能的快速发展,如何构建高效的数据科学平台已经成为一个重要议题.Apache Zeppelin是一个开源的数据科学平台,其提供了一种简单.高效的方式来处理和存储数据,并且 ...
- 基于.NetCore开发博客项目 StarBlog - (29) 开发RSS订阅功能
前言 最近忙中偷闲把博客的评论功能给做完了,我可以说这个评论功能已经达到「精致」的程度了 但在正式发布之前,先卖个关子,来介绍一下另一个新功能--RSS订阅 RSS是啥 来自hk gov新闻网的介绍~ ...
- vivo 自研鲁班分布式 ID 服务实践
作者:vivo IT 平台团队- An Peng 本文介绍了什么是分布式ID,分布式ID的业务场景以及9种分布式ID的实现方式,同时基于vivo内部IT的业务场景,介绍了自研鲁班分布式ID服务的实践. ...
- 说说 Go 语言的坑(二)
上一篇文章 说说 Go 语言 for-range 的坑 说的是 for-range 的,工作中,其实还是遇到蛮多奇奇怪怪的问题,这里也顺便整理了一下,就当作是续集:) 先继续看 for-range 的 ...
- 河南省第十四届icpc大学生程序设计竞赛-clk
这次比赛赛程比较长,520出发,521,回学校,出发的那一天有点热,感觉不是很好,而且那一天感觉有点生病,应该只是普通感冒,热身赛的时候被oier吊打,省实验真厉害,晚上回酒店后,我喊队友,补了前年的 ...
- .NET for Apache Spark 调试
.NET for Apache Spark 调试 官方文档:在 Windows 上部署 .NET for Apache Spark 应用程序 | Microsoft Learn 打开"新命令 ...
- Centos7中Jar快速启动脚本
Centos7中Jar快速启动脚本 创建一个文本,将以下脚本内容复制到文本当中,重命名文本后缀为.sh 注意:根据自己的项目进行更改相关内容,对应注释已说明 #!/bin/sh APP_NAME=ma ...
- iptables防火墙调试,想打印个日志就这么难
背景 怎么会讲这个话题,这个说来真的长了.但是,长话短说,也是可以的. 我前面的文章提到,线上的服务用了c3p0数据库连接池,会偶发连接泄露问题,而分析到最后,又怀疑是db侧主动关闭连接,或者是服务所 ...