Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Erro ao usar pymysql no frasco


Em primeiro lugar, você precisa decidir se deseja manter uma conexão persistente com o MySQL. Este último tem melhor desempenho, mas precisa de um pouco de manutenção.

Padrão wait_timeout no MySQL é de 8 horas. Sempre que uma conexão estiver ociosa por mais de wait_timeout ele está fechado. Quando o servidor MySQL é reiniciado, ele também fecha todas as conexões estabelecidas. Assim, se você usar uma conexão persistente, você precisa verificar antes de usar uma conexão se ela está ativa (e se não, reconecte). Se você usa conexão por solicitação, não precisa manter o estado de conexão, pois as conexões são sempre atualizadas.

Conexão por solicitação


Uma conexão de banco de dados não persistente tem uma sobrecarga evidente de abertura de conexão, handshaking e assim por diante (para servidor de banco de dados e cliente) por cada solicitação HTTP recebida.

Aqui está uma citação do tutorial oficial do Flask sobre conexões de banco de dados :

Observe, no entanto, que o contexto do aplicativo é inicializado por solicitação (o que é meio que velado por preocupações de eficiência e a linguagem do Flask). E assim, ainda é muito ineficiente. No entanto, deve resolver o seu problema. Aqui está um trecho despojado do que sugere como aplicado ao 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)

Conexão persistente


Para uma conexão de banco de dados de conexão persistente, existem duas opções principais. Você tem um conjunto de conexões ou mapeia conexões para processos de trabalho. Como normalmente os aplicativos Flask WSGI são servidos por servidores encadeados com número fixo de encadeamentos (por exemplo, uWSGI), o mapeamento de encadeamentos é mais fácil e eficiente.

Há um pacote, DBUtils , que implementa ambos, e PersistentDB para conexões mapeadas por rosca.

Uma advertência importante na manutenção de uma conexão persistente são as transações. A API para reconexão é ping . É seguro para auto-commit instruções únicas, mas pode atrapalhar entre uma transação (um pouco mais de detalhes aqui ). DBUtils cuida disso e só deve se reconectar em dbapi.OperationalError e dbapi.InternalError (por padrão, controlado por failures para inicializador de PersistentDB ) levantadas fora de uma transação.

Veja como o snippet acima ficará com o 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)

Micro-benchmark


Para dar uma pequena pista de quais são as implicações de desempenho em números, aqui está o micro-benchmark.

eu corri:
  • 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

E testou-os com simultaneidade 1, 4, 8, 16 via:
siege -b -t 15s -c 16 http://localhost:5000/?city=london



Observações (para minha configuração local):
  1. Uma conexão persistente é aproximadamente 30% mais rápida,
  2. Na simultaneidade 4 e superior, o processo de trabalho do uWSGI atinge mais de 100% da utilização da CPU (pymysql tem que analisar o protocolo MySQL em Python puro, que é o gargalo),
  3. Na simultaneidade 16, mysqld A utilização da CPU do é ~55% por solicitação e ~45% para conexão persistente.