sql >> Database >  >> RDS >> Mysql

Fout bij gebruik van pymysql in kolf

Allereerst moet u beslissen of u een permanente verbinding met MySQL wilt behouden. De laatste presteert beter, maar heeft wat onderhoud nodig.

Standaard wait_timeout in MySQL is 8 uur. Wanneer een verbinding langer inactief is dan wait_timeout het is gesloten. Wanneer de MySQL-server opnieuw wordt opgestart, worden ook alle tot stand gebrachte verbindingen gesloten. Als u dus een permanente verbinding gebruikt, moet u voordat u een verbinding gebruikt, controleren of deze actief is (en zo niet, opnieuw verbinding maken). Als u verbinding per verzoek gebruikt, hoeft u de verbindingsstatus niet te behouden, omdat verbindingen altijd vers zijn.

Per verzoek verbinding

Een niet-permanente databaseverbinding heeft duidelijke overhead van het openen van de verbinding, handshaking, enzovoort (voor zowel de databaseserver als de client) per inkomend HTTP-verzoek.

Hier is een citaat uit Flask's officiële tutorial betreffende databaseverbindingen :

Houd er echter rekening mee dat toepassingscontext wordt geïnitialiseerd per verzoek (wat een beetje versluierd is door efficiëntieproblemen en Flask's jargon). En daardoor is het nog steeds erg inefficiënt. Het zou echter uw probleem moeten oplossen. Hier is een gestript fragment van wat het suggereert zoals toegepast op 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)

Persistente verbinding

Voor een permanente verbinding met de databaseverbinding zijn er twee belangrijke opties. Of u hebt een pool van verbindingen of brengt verbindingen met werkprocessen in kaart. Omdat Flask WSGI-applicaties normaal gesproken worden bediend door threaded servers met een vast aantal threads (bijv. uWSGI), is het in kaart brengen van threads eenvoudiger en even efficiënt.

Er is een pakket, DBUtils , die beide implementeert, en PersistentDB voor thread-mapped verbindingen.

Een belangrijk voorbehoud bij het onderhouden van een aanhoudende verbinding zijn transacties. De API voor opnieuw verbinden is ping . Het is veilig voor het automatisch vastleggen van afzonderlijke verklaringen, maar het kan storend zijn tussen een transactie in (een beetje meer details hier ). DBUtils zorgt hiervoor en mag alleen opnieuw verbinding maken op dbapi.OperationalError en dbapi.InternalError (standaard gecontroleerd door failures naar initialisatie van PersistentDB ) opgehaald buiten een transactie om.

Zo ziet het bovenstaande fragment eruit met 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

Om een ​​idee te geven van de implicaties voor prestaties in cijfers, volgt hier een microbenchmark.

Ik rende:

  • 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

En testte ze met concurrency 1, 4, 8, 16 via:

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

Opmerkingen (voor mijn lokale configuratie):

  1. Een permanente verbinding is ~30% sneller,
  2. Bij gelijktijdigheid 4 en hoger piekt het uWSGI-werkproces met meer dan 100% van het CPU-gebruik (pymysql moet het MySQL-protocol in pure Python ontleden, wat het knelpunt is),
  3. Op gelijktijdigheid 16, mysqld 's CPU-gebruik is ~55% voor per-verzoek en ~45% voor aanhoudende verbinding.


  1. Voorbeelden van het converteren van 'time' naar 'datetime' in SQL Server (T-SQL)

  2. SpringBoot MySQL JDBC Kan eerste verbindingen van pool niet maken

  3. SQL-groeperen op:logica gebruiken om resultaten te filteren op basis van aggregatiefuncties

  4. meerdere kolommen instellen met één update