1. 程式人生 > 資料庫 >解決mysql伺服器在無操作超時主動斷開連線的情況

解決mysql伺服器在無操作超時主動斷開連線的情況

我們在使用mysql服務的時候,正常情況下,mysql的設定的timeout是8個小時(28800秒),也就是說,如果一個連線8個小時都沒有操作,那麼mysql會主動的斷開連線,當這個連線再次嘗試查詢的時候就會報個”MySQL server has gone away”的誤,但是有時候,由於mysql伺服器那邊做了一些設定,很多情況下會縮短這個連線timeout時長以保證更多的連線可用。有時候設定得比較變態,很短,30秒,這樣就需要客戶端這邊做一些操作來保證不要讓mysql主動來斷開。

檢視mysql的timeout

使用客戶端工具或者Mysql命令列工具輸入show global variables like '%timeout%';就會顯示與timeout相關的屬性,這裡我用docker模擬了一個測試環境。

mysql> show variables like '%timeout%'; 
+-----------------------------+----------+
| Variable_name        | Value  |
+-----------------------------+----------+
| connect_timeout       | 10    |
| delayed_insert_timeout   | 300   |
| have_statement_timeout   | YES   |
| innodb_flush_log_at_timeout | 1    |
| innodb_lock_wait_timeout  | 50    |
| innodb_rollback_on_timeout | OFF   |
| interactive_timeout     | 30    |
| lock_wait_timeout      | 31536000 |
| net_read_timeout      | 30    |
| net_write_timeout      | 60    |
| rpl_stop_slave_timeout   | 31536000 |
| slave_net_timeout      | 60    |
| wait_timeout        | 30    |
+-----------------------------+----------+
13 rows in set

wait_timeout:伺服器關閉非互動連線之前等待活動的秒數,就是你在你的專案中進行程式呼叫

interactive_timeout: 伺服器關閉互動式連線前等待活動的秒數,就是你在你的本機上開啟mysql的客戶端,cmd的那種

使用pymysql進行查詢

我在資料庫裡隨便建立了一個表,插入兩條資料

mysql> select * from person;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | yang | 18 |
| 2 | fan | 16 |
+----+------+-----+
2 rows in set

我使用pymysql這個庫對其進行查詢操作,很簡單

#coding:utf-8
import pymysql

def mytest():
  connection = pymysql.connect(
  host='localhost',port=3306,user='root',password='123456',db='mytest',charset='utf8')

  cursor = connection.cursor()
  cursor.execute("select * from person")
  data = cursor.fetchall()
  cursor.close()
  for i in data:
    print(i)
  cursor.close()
  connection.close()

if __name__ == '__main__':
  mytest()

可以正確的得到結果

(1,'yang',18)

(2,'fan',16)

連線超時以後的查詢

上面可以正常得到結果是由於當建立好一個連結以後,就立刻進行了查詢,此時還沒有超過它的超時時間,如果我sleep一段時間,看看什麼效果。

#coding:utf-8

import pymysql
import time


def mytest():
  connection = pymysql.connect(
  host='localhost',charset='utf8')
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data = cursor.fetchall()
  for i in data:
    print(i)
  cursor.close()

  time.sleep(31)
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data2 = cursor.fetchall()
  for i in data2:
    print(i)
  cursor.close()
  connection.close()

if __name__ == '__main__':
  mytest()

這裡進行了兩次查詢,因為我把mysql的wait_timeout設定了30秒,所以我在第一次查詢之後停了31秒,目的讓mysql服務主動的和我剛才建立的連線斷開,得到的結果是

(1,18)
(2,16)
Traceback (most recent call last):
 File "F:/python/python3Test/mysqltest.py",line 29,in <module>
  mytest()
 File "F:/python/python3Test/mysqltest.py",line 22,in mytest
  cursor.execute("select * from person")
 ...
 ...
 File "C:\Python35\lib\site-packages\pymysql\connections.py",line 702,in _read_bytes
  CR.CR_SERVER_LOST,"Lost connection to MySQL server during query")
pymysql.err.OperationalError: (2013,'Lost connection to MySQL server during query')

Process finished with exit code 1

可以看到在停了31秒鐘以後,再次使用該連線進行查詢將丟擲2013,'Lost connection to MySQL server during query'錯誤。

解決辦法

解決的方法有兩種,既然這裡的超時是由於在規定時間內沒有任何操作導致mysql主動的將連結關閉,pymysql的connection物件有一個ping()方法,可以檢查連線是否有效,在每次執行查詢操作之前先執行一下ping()方法,該方法預設的有個reconnect引數,預設是True,如果失去連線了會重連。

#coding:utf-8

import pymysql
import time


def mytest():
  connection = pymysql.connect(
  host='localhost',charset='utf8')
  connection.ping()
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data = cursor.fetchall()
  for i in data:
    print(i)
  cursor.close()
  
  time.sleep(31)
  connection.ping()
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data2 = cursor.fetchall()
  for i in data2:
    print(i)
  cursor.close()
  connection.close()

if __name__ == '__main__':
  mytest()

我曾嘗試使用另外一個執行緒不停來執行ping()操作,但是當我這樣做以後連線就會丟失,之後的操作就不能進行了。這個問題我再研究研究。

#coding:utf-8

import pymysql
import time
import threading
import traceback

def ping(conn):
  while True:
    try:      
      conn.ping()
    except:
      print(traceback.format_exc())
    finally:
      time.sleep(1)

def mytest():
  connection = pymysql.connect(
  host='localhost',charset='utf8')
  cursor = connection.cursor()
  # 放在此處不行,必須等待cursor的一個execute之後再執行才可以
  # th = threading.Thread(target=ping,args=(connection,))
  # th.setDaemon(True)
  # th.start()
  cursor.execute("select * from person")
  data = cursor.fetchall()
  for i in data:
    print(i)
  cursor.close()

  # 執行緒放在此處啟動可以
  th = threading.Thread(target=ping,))
  th.setDaemon(True)
  th.start()
  
  time.sleep(31)
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data2 = cursor.fetchall()
  for i in data2:
    print(i)
  cursor.close()
  connection.close()

if __name__ == '__main__':
  mytest()

還有一種方法是使用連線池,連線池中保持著指定數量的可用連線,每次重新獲取一個有效的連線進行查詢操作,pymysql本身不具有連線池功能,需要借住DBUtils

#coding:utf-8
import pymysql
import time
from DBUtils.PooledDB import PooledDB,SharedDBConnection


def mytest():
  pool = PooledDB(
    creator=pymysql,# 初始化時,連線池至少建立的空閒連線,0表示不建立
    maxconnections=3,# 連線池中空閒的最多連線數,0和None表示沒有限制    
    mincached=2,# 連線池中最多共享的連線數量,0和None表示全部共享(其實沒什麼卵用)
    maxcached=5,maxshared=3,host='localhost',charset='utf8'
  )
  connection = pool.connection()
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data = cursor.fetchall()
  for i in data:
    print(i)

  time.sleep(40)
  cursor.execute("select * from person")
  data2 = cursor.fetchall()
  for i in data2:
    print(i)
  cursor.close()
  connection.close()

if __name__ == '__main__':
  mytest()

這種方式雖然可以正確的獲取結果,但是實際的專案中並不會這麼使用,而是在執行完查詢語句以後要將connection關閉,注意這裡的關閉並不是真正的關閉,而只是將連線返回給連線池讓其它人使用.

#coding:utf-8
import pymysql
import time
from DBUtils.PooledDB import PooledDB,maxconnections=3,0表示不建立
    mincached=2,0和None表示沒有限制
    maxcached=5,0和None表示全部共享(其實沒什麼卵用)
    maxshared=3,charset='utf8'
  )
  connection = pool.connection()
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data = cursor.fetchall()
  for i in data:
    print(i)
  cursor.close()
  # 關閉連線,其實並沒有真正關閉,只是將連線返回給連線池
  connection.close()

  time.sleep(40)
  connection = pool.connection()
  cursor = connection.cursor()
  cursor.execute("select * from person")
  data2 = cursor.fetchall()
  for i in data2:
    print(i)
  cursor.close()
  connection.close()

if __name__ == '__main__':
  mytest()

以上這篇解決mysql伺服器在無操作超時主動斷開連線的情況就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。