在烧瓶中使用pymysql时出错

时间:2017-12-08 09:51:17

标签: python mysql flask pymysql

我正在使用pymysql客户端连接到我的烧瓶API中的mysql, 一切都运行良好几天(大约1-2天)之后突然它开始抛出这个错误

Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/pymysql/connections.py", line 1039, in _write_bytes
    self._sock.sendall(data)
TimeoutError: [Errno 110] Connection timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "Main.py", line 194, in post
    result={'resultCode':100,'resultDescription':'SUCCESS','result':self.getStudentATData(studentId,args['chapterId'])}
  File "Main.py", line 176, in getStudentATData
    cur.execute("my query")
  File "/usr/local/lib/python3.4/dist-packages/pymysql/cursors.py", line 166, in execute
    result = self._query(query)
  File "/usr/local/lib/python3.4/dist-packages/pymysql/cursors.py", line 322, in _query
    conn.query(q)
  File "/usr/local/lib/python3.4/dist-packages/pymysql/connections.py", line 855, in query
    self._execute_command(COMMAND.COM_QUERY, sql)
  File "/usr/local/lib/python3.4/dist-packages/pymysql/connections.py", line 1092, in _execute_command
    self._write_bytes(packet)
  File "/usr/local/lib/python3.4/dist-packages/pymysql/connections.py", line 1044, in _write_bytes
    "MySQL server has gone away (%r)" % (e,))
pymysql.err.OperationalError: (2006, "MySQL server has gone away (TimeoutError(110, 'Connection timed out'))")

如果重新启动应用程序它再次正常工作,我已经尝试了一切,但似乎无法摆脱这个,任何人都可以帮忙吗? 正如所建议的那样,我实施了一种重试机制,但这并没有解决问题

def connect(self):
        #db connect here
    def cursor(self):
        try:
            cursor = self.conn.cursor()
        except Exception as e:
            print(e)
            self.connect()
            cursor = self.conn.cursor()
        return cursor

像DB()。cursor()

一样消耗它

4 个答案:

答案 0 :(得分:2)

首先,您需要决定是否要保持与MySQL的持久连接。后者表现更好,但需要一点维护。

MySQL中的默认wait_timeout为8小时。每当连接空闲时间超过wait_timeout时,它就会关闭。重新启动MySQL服务器时,它还会关闭所有已建立的连接。因此,如果您使用持久连接,则需要在使用连接之前进行检查(如果没有,请重新连接)。如果您使用每个请求连接,则不需要维护连接状态,因为连接始终是新鲜的。

每个请求连接

非持久性数据库连接对于每个传入的HTTP请求打开连接,握手等(对于数据库服务器和客户端)都有明显的开销。

以下是Flask官方教程regarding database connections的引用:

  

始终创建和关闭数据库连接的效率非常低,因此您需要将其保留更长时间。由于数据库连接封装了事务,因此您需要确保一次只有一个请求使用该连接。一种优雅的方法是利用应用程序上下文。

但请注意,每个请求都会初始化应用程序上下文(这有点被效率问题和Flask的术语所掩盖)。因此,它仍然非常低效。但它应该解决您的问题。以下是适用于pymysql的建议摘要:

import pymysql
from flask import Flask, g, request    

app = Flask(__name__)    

def connect_db():
    return pymysql.connect(
        user = 'guest', password = '', database = 'sakila', 
        autocommit = True, charset = 'utf8mb4', 
        cursorclass = pymysql.cursors.DictCursor)

def get_db():
    '''Opens a new database connection per request.'''        
    if not hasattr(g, 'db'):
        g.db = connect_db()
    return g.db    

@app.teardown_appcontext
def close_db(error):
    '''Closes the database connection at the end of request.'''    
    if hasattr(g, 'db'):
        g.db.close()    

@app.route('/')
def hello_world():
    city = request.args.get('city')

    cursor = get_db().cursor()
    cursor.execute('SELECT city_id FROM city WHERE city = %s', city)
    row = cursor.fetchone()

    if row:
      return 'City "{}" is #{:d}'.format(city, row['city_id'])
    else:
      return 'City "{}" not found'.format(city)

持久连接

对于持久连接数据库连接,有两个主要选项。您有一个连接池或映射到工作进程的连接。因为通常Flask WSGI应用程序由具有固定线程数的线程服务器(例如uWSGI)提供服务,所以线程映射更容易且更有效。

有一个包DBUtils,它实现了两者,PersistentDB用于线程映射连接。

维护持久连接的一个重要警告是事务。重新连接的A​​PI为ping。自动提交单个语句是安全的,但它可能会在事务之间中断(更多细节here)。 DBUtils会处理此问题,并且只应在dbapi.OperationalErrordbapi.InternalError(默认情况下,由failures控制到PersistentDB的初始化)重新连接到事务之外。

上面的代码段如何与PersistentDB一样。

import pymysql
from flask import Flask, g, request
from DBUtils.PersistentDB import PersistentDB    

app = Flask(__name__)    

def connect_db():
    return PersistentDB(
        creator = pymysql, # the rest keyword arguments belong to pymysql
        user = 'guest', password = '', database = 'sakila', 
        autocommit = True, charset = 'utf8mb4', 
        cursorclass = pymysql.cursors.DictCursor)

def get_db():
    '''Opens a new database connection per app.'''

    if not hasattr(app, 'db'):
        app.db = connect_db()
    return app.db.connection()    

@app.route('/')
def hello_world():
    city = request.args.get('city')

    cursor = get_db().cursor()
    cursor.execute('SELECT city_id FROM city WHERE city = %s', city)
    row = cursor.fetchone()

    if row:
      return 'City "{}" is #{:d}'.format(city, row['city_id'])
    else:
      return 'City "{}" not found'.format(city)

微基准测试

为了给出一些关于数字性能影响的线索,这里是微观基准。

我跑了:

  • uwsgi --http :5000 --wsgi-file app_persistent.py --callable app --master --processes 1 --threads 16
  • uwsgi --http :5000 --wsgi-file app_per_req.py --callable app --master --processes 1 --threads 16

使用并发1,4,8,16对它们进行负载测试:

siege -b -t 15s -c 16 http://localhost:5000/?city=london

chart

观察(对于我的本地配置):

  1. 持久连接速度提高约30%,
  2. 在并发4和更高版本上,uWSGI工作进程达到CPU利用率超过100%的峰值(pymysql必须解析纯Python中的MySQL协议,这是瓶颈),
  3. 在并发16上,mysqld的每个请求的CPU利用率约为55%,持久连接的CPU利用率约为45%。

答案 1 :(得分:1)

如我所见,您有两种选择:

  • 为每个查询创建新连接,然后关闭它。像这样:

    def predict():
        img = cv2.imread('C:/Users/HABITUS/Desktop/arvin files/download.jpg') #the url of the image selected must be here!!
        img = cv2.resize(img, (600, 600))
        enhancer = Image.fromarray(img)
        enhancer = ImageEnhance.Contrast(enhancer)
        enhanced = enhancer.enhance(1.5)
        enhancer1 = ImageEnhance.Brightness(enhanced).enhance(1.3)
        convert = scipy.misc.fromimage(enhancer1)
        imgM = cv2.medianBlur(convert, 5)
        # blurring and smoothening
        kernel = np.ones((5, 5), np.uint8)
        erosion = cv2.erode(imgM, kernel, iterations=1)
        dilation = cv2.dilate(erosion, kernel, iterations=1)
        blur = cv2.GaussianBlur(convert, (15, 15), 10)
        grayscaled = cv2.cvtColor(imgM, cv2.COLOR_BGR2GRAY)
    
        retval2, threshold2 = cv2.threshold(grayscaled, 200, 1, cv2.THRESH_BINARY_INV)
    
        gaus = cv2.adaptiveThreshold(grayscaled, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 115, 1)
        retval2, otsu = cv2.threshold(grayscaled, 140, 250, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        backtorgb = cv2.cvtColor(threshold2, cv2.COLOR_GRAY2RGB)
        mult = cv2.multiply(img, backtorgb)
        edges = cv2.Canny(threshold2, 120, 50)
    
  • 更好的方法是使用像SqlAlchemy.pool这样的连接池和pool_pre_ping参数以及自定义连接函数。

答案 2 :(得分:0)

我不相信这是Flask / pymysql的问题,因为它是MySQL超时配置的症状。我假设这是某种云数据库实例?

看看这个:

https://dba.stackexchange.com/questions/1558/how-long-is-too-long-for-mysql-connections-to-sleep

我在那里发布您的问题并提供有关您的设置的详细信息,您可能会得到配置答案。

Python解决方案是使用sqlalchemy& amp; flask-sqlalchemy然后设置配置变量SQLALCHEMY_POOL_RECYCLE = 3600以在一小时后(或您想要的任何值)回收连接。或者,如果您不想在项目中添加那么多批量,则可以实现连接"计时器"功能,以在后台自行回收连接:

from datetime import datetime, timedelta

class MyConnectionPool(object)
    """Class that returns a database connection <= 1 hour old"""
    refresh_time = timedelta(hours=1)

    def __init__(self, host, user, pass):
        self.host = host
        self.user = user
        self.pass = pass

        self.db = self._get_connection()

    @property
    def connection(self):

        if self.refresh <= datetime.now():
            self.db = self._get_connection()

        return self.db

    def _get_connection(self):
        self.refresh = datetime.now() + self.refresh_time
        return pymysql.connect(
            host=self.host,
            user=self.user,
            passwd=self.pass
        )

答案 3 :(得分:0)

您是否尝试过进行dB ping,如果在每次通话之前重新连接失败? 我在烧瓶中发现的另一件事是,如果我在每次通话后没有关闭连接,我最终会遇到这样的情况。

很抱歉由于缺乏细节但是我在手机上输入这个并滚动浏览所有代码已经有了工作:-)

class MyDatabase():
    def __init__(self, host, user, passwd, db, charset):
        self.host = host
        self.usr = user
        self.passwd = passwd
        self.db = db
        self.curclass = pymysql.cursors.DictCursor
        self.charset = charset
        self.connection = pymysql.connect(host=self.host, user=self.usr, passwd=self.passwd, db=self.db,
                                      cursorclass=self.curclass, charset=self.charset)


    def get_keywords(self):
        self.connection.connect()
        cur = self.connection.cursor()
        sql = """
    SELECT * FROM input_keywords
    """
        rows = None
        try:
            cur.execute(sql)
            rows = cur.fetchall()
        except Exception as e:
            print(e)
            self.connection.rollback()
        finally:
            cur.close()
            self.connection.commit()
            self.connection.close()
        return rows

然后让Flask为每个请求创建一个连接,并在最后关闭它。

所以我调用的任何方法都使用这种模式。这也允许多个请求等(网站这样做)

我并不是说它是完美的,但是对于数据库的每个请求,您正在创建并关闭数据库连接,因此它永远不会超时。

这是非常基本的,同样,你可以将它与ping()结合使用,并允许它创建一个新的连接等