sql >> Databasteknik >  >> RDS >> Mysql

Fel vid användning av pymysql i kolven

Först och främst måste du bestämma dig för om du vill behålla en beständig anslutning till MySQL. Den senare presterar bättre, men behöver lite underhåll.

Standard wait_timeout i MySQL är 8 timmar. När en anslutning är inaktiv längre än wait_timeout det är stängt. När MySQL-servern startas om stänger den också alla etablerade anslutningar. Om du använder en beständig anslutning måste du alltså kontrollera innan du använder en anslutning om den är vid liv (och om inte, återanslut). Om du använder en anslutning per begäran behöver du inte upprätthålla anslutningsläget, eftersom anslutningen alltid är uppdaterad.

Anslutning per begäran

En icke-beständig databasanslutning har uppenbar overhead för öppning av anslutning, handskakning och så vidare (för både databasserver och klient) för varje inkommande HTTP-förfrågan.

Här är ett citat från Flasks officiella handledning angående databasanslutningar :

Observera dock att applikationskontext initieras per förfrågan (vilket är lite täckt av effektivitetsproblem och Flasks språkspråk). Och därför är det fortfarande väldigt ineffektivt. Det borde dock lösa ditt problem. Här är avskalat utdrag av vad det föreslår tillämpat på 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)

Ihållande anslutning

För en beständig anslutningsdatabasanslutning finns det två huvudalternativ. Antingen har du en pool av kopplingar eller mappar kopplingar till arbetsprocesser. Eftersom Flask WSGI-applikationer normalt betjänas av gängade servrar med ett fast antal trådar (t.ex. uWSGI), är trådmappning enklare och lika effektiv.

Det finns ett paket, DBUtils , som implementerar både och PersistentDB för trådmappade anslutningar.

En viktig varning för att upprätthålla en beständig anslutning är transaktioner. API:et för återanslutning är ping . Det är säkert att automatiskt utföra enstaka påståenden, men det kan störa mellan en transaktion (lite mer information här ). DBUtils tar hand om detta och bör endast återansluta på dbapi.OperationalError och dbapi.InternalError (som standard kontrolleras av failures till initialiserare av PersistentDB ) höjts utanför en transaktion.

Så här kommer kodavsnittet ovan att se ut med 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)

Mikro-benchmark

För att ge en liten aning om vilka resultatkonsekvenser är i siffror, här är ett mikroriktmärke.

Jag sprang:

  • 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

Och lasttestade dem med samtidighet 1, 4, 8, 16 via:

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

Observationer (för min lokala konfiguration):

  1. En beständig anslutning är ~30 % snabbare,
  2. Vid samtidighet 4 och högre toppar uWSGI-arbetarprocessen med över 100 % av CPU-användningen (pymysql måste analysera MySQL-protokollet i ren Python, vilket är flaskhalsen),
  3. Vid samtidighet 16, mysqld CPU-användningen är ~55 % för per begäran och ~45 % för beständig anslutning.


  1. MySQL skapa tid och uppdatera tidsstämpel

  2. Det effektivaste sättet att välja tusentals rader från en lista med ID

  3. Få handtag till inbyggd Oracle Connection i Hibernate 4 för att köra en lagrad proc

  4. Hanterar tider och efter midnatt