Først og fremmest skal du beslutte, om du vil bevare en vedvarende forbindelse til MySQL. Sidstnævnte klarer sig bedre, men har brug for lidt vedligeholdelse.
Standard wait_timeout
i MySQL er 8 timer. Når en forbindelse er inaktiv længere end wait_timeout
den er lukket. Når MySQL-serveren genstartes, lukker den også alle etablerede forbindelser. Så hvis du bruger en vedvarende forbindelse, skal du kontrollere, før du bruger en forbindelse, om den er i live (og hvis ikke, genopret forbindelse). Hvis du bruger en forbindelse pr. anmodning, behøver du ikke at opretholde forbindelsestilstanden, fordi forbindelsen altid er frisk.
Tilslutning pr. anmodning
En ikke-vedvarende databaseforbindelse har tydelige omkostninger ved åbning af forbindelse, handshaking og så videre (for både databaseserver og klient) for hver indkommende HTTP-anmodning.
Her er et citat fra Flasks officielle selvstudie om databaseforbindelser :
Bemærk dog, at applikationskontekst initialiseres pr. anmodning (hvilket er lidt tilsløret af effektivitetsproblemer og Flasks lingo). Og derfor er det stadig meget ineffektivt. Det burde dog løse dit problem. Her er strippet uddrag af, hvad det foreslår anvendt 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)
Vedholdende forbindelse
For en vedvarende forbindelse til databasen er der to hovedmuligheder. Enten har du en pulje af forbindelser eller kortlægger forbindelser til arbejdsprocesser. Fordi Flask WSGI-applikationer normalt betjenes af trådede servere med et fast antal tråde (f.eks. uWSGI), er trådkortlægning nemmere og lige så effektivt.
Der er en pakke, DBUtils
, som implementerer både og PersistentDB
for tråd-mappede forbindelser.
Et vigtigt forbehold for at opretholde en vedvarende forbindelse er transaktioner. API'et til genforbindelse er ping
. Det er sikkert at automatisk foretage enkeltudsagn, men det kan forstyrre mellem en transaktion (lidt flere detaljer her
). DBUtils tager sig af dette og bør kun oprette forbindelse igen på dbapi.OperationalError
og dbapi.InternalError
(som standard styret af failures
til initialisering af PersistentDB
) hævet uden for en transaktion.
Sådan ser det ovenstående uddrag ud 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
For at give et lille fingerpeg om, hvilke præstationsimplikationer der er i tal, er her et mikrobenchmark.
Jeg løb:
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
Og belastningsteste dem med samtidighed 1, 4, 8, 16 via:
siege -b -t 15s -c 16 http://localhost:5000/?city=london
Observationer (for min lokale konfiguration):
- En vedvarende forbindelse er ~30 % hurtigere,
- Ved samtidighed 4 og højere topper uWSGI-arbejdsprocessen med over 100 % af CPU-udnyttelsen (
pymysql
skal parse MySQL-protokollen i ren Python, som er flaskehalsen), - Ved samtidighed 16,
mysqld
's CPU-udnyttelse er ~55% for pr. anmodning og ~45% for vedvarende forbindelse.