思路:
1 python mysql 的cping 函數會校驗鏈接的可用性,如果連接不可用將會產生異常
2 利用這一特性,構造一個連接丟失的循環,不斷嘗試連接數據庫,直到連接恢復
3 使用這樣的機制不需要關閉數據庫功能,對於駐留進程,有大量數據進行寫操作時,很有用途
#!/usr/bin/env python # -*-coding:UTF-8-*- import sys, MySQLdb, traceback import time class mysql: def __init__(self, host='', user='', passwd='', db='', port=3306, charset='utf8' ): self.host = host self.user = user self.passwd = passwd self.db = db self.port = port self.charset = charset self.conn = None self._conn() def _conn(self): try: self.conn = MySQLdb.Connection(self.host, self.user, self.passwd, self.db, self.port, self.charset) return True except: return False def _reConn(self, num=28800, stime=3): # 重試連接總次數為1天,這里根據實際情況自己設置,如果服務器宕機1天都沒發現就...... _number = 0 _status = True while _status and _number <= num: try: self.conn.ping() # cping 校驗連接是否異常 _status = False except: if self._conn() == True: # 重新連接,成功退出 _status = False break _number += 1 time.sleep(stime) # 連接不成功,休眠3秒鍾,繼續循環,知道成功或重試次數結束 def select(self, sql=''): try: self._reConn() self.cursor = self.conn.cursor(MySQLdb.cursors.DictCursor) self.cursor.execute(sql) result = self.cursor.fetchall() self.cursor.close() return result except MySQLdb.Error, e: # print "Error %d: %s" % (e.args[0], e.args[1]) return False def select_limit(self, sql='', offset=0, length=20): sql = '%s limit %d , %d ;' % (sql, offset, length) return self.select(sql) def query(self, sql=''): try: self._reConn() self.cursor = self.conn.cursor(MySQLdb.cursors.DictCursor) self.cursor.execute("set names utf8") # utf8 字符集 result = self.cursor.execute(sql) self.conn.commit() self.cursor.close() return (True, result) except MySQLdb.Error, e: return False def close(self): self.conn.close() if __name__ == '__main__': my = mysql('localhost', 'root', 'root', 'test', 3306) print my.select_limit('select * from a_table', 1, 1) # my.close()
test
后台服務在運行時發現一個問題,運行一段時間后,接口請求報錯;
pymysql.err.InterfaceError: (0, '')
排查到原因是數據庫操作對象實例未注銷,但是持有的數據庫連接已經過期,導致后續數據庫操作不能正常進行;
出現問題的代碼
class MysqlConnection(object):
"""
mysql操作類,對mysql數據庫進行增刪改查
"""
def __init__(self, config):
# Connect to the database
self.connection = pymysql.connect(**config)
self.cursor = self.connection.cursor()
def Query(self, sql):
"""
查詢數據
:param sql:
:return:
"""
self.cursor.execute(sql)
return self.cursor.fetchall()
在分析問題前,先看看Python 數據庫的Connection、Cursor兩大對象
Python 數據庫
Connection()的參數列表
host,連接的數據庫服務器主機名,默認為本地主機(localhost)
user,連接數據庫的用戶名,默認為當前用戶
passwd,連接密碼,沒有默認值
db,連接的數據庫名,沒有默認值
conv,將文字映射到Python類型的字典
cursorclass,cursor()使用的種類,默認值為MySQLdb.cursors.Cursor
compress,啟用協議壓縮功能
named_pipe,在windows中,與一個命名管道相連接
init_command,一旦連接建立,就為數據庫服務器指定一條語句來運行
read_default_file,使用指定的MySQL配置文件
read_default_group,讀取的默認組
unix_socket,在unix中,連接使用的套接字,默認使用TCP
port,指定數據庫服務器的連接端口,默認是3306
排查:可能是因為對象屬性cursor引起的
網上有提到cursor引起的,其實不一定,為了排除這個假設,我在每個操作數據庫的方法里都重新引用connect.curser()
cursor = connection.cursor()
cursor.execute(query)
cursor.close()
去掉__init__
方法里的self.cursor,運行一段時間后,還是出現異常,所以可以斷定跟cursor沒有關系;
##排查:查看數據庫對象
調試代碼,將超時時間設置較長
self.connection._write_timeout = 10000
發現並沒有生效,使用try…except… 方法捕獲失敗后重新連接數據庫
try:
self.cursor.execute(sql)
except:
self.connection()
self.cursor.execute(sql)
直接拋出異常,並沒有執行except代碼段
打印self.connection
,輸出如下:
<pymysql.connections.Connection object at 0x0000000003E2CCC0>
拋出異常重新connect是不行的,因為connections 仍存在未失效,也就是我們找到的原因:對象持有的數據庫連接斷開了
解決
找到一種方法可以解決問題,在每次連接之前,判斷該鏈接是否有效,pymysql提供的接口是 Connection.ping()
#這個該方法的源碼
def ping(self, reconnect=True):
"""Check if the server is alive"""
if self._sock is None:
if reconnect:
self.connect()
reconnect = False
else:
raise err.Error("Already closed")
try:
self._execute_command(COMMAND.COM_PING, "")
return self._read_ok_packet()
except Exception:
if reconnect:
self.connect()
return self.ping(False)
else:
raise
所以每次處理數據庫的時候,可以這么操作,現實發現方案有效;
class DataSource(object):
def __init__(self):
self.conn = self.to_connect()
def __del__(self):
self.conn.close()
def to_connect(self):
return pymysql.connections.Connection(**params)
def is_connected(self):
"""Check if the server is alive"""
try:
self.conn.ping(reconnect=True)
print "db is connecting"
except:
traceback.print_exc()
self.conn = self.to_connect()
print "db reconnect"
補充
數據庫之所以斷開連接,是因為數據庫默認的wait_timeout=28800
,這個單位是秒,換算后是8小時,也就是原來我的服務啟動8小時后,就會被mysql自動斷開,如果我沒有重連機制那就真的是不能用,這個不像java的一些orm框架都能做連接存活處理,其實python對應的orm框架sqlalchemy也有這個處理,但是我選擇了pymysql,所以需要自己處理。
--查詢數據庫的各項超時指標
show variables like '%timeout%';