На първо място, трябва да решите дали искате да поддържате постоянна връзка с MySQL. Последният работи по-добре, но се нуждае от малко поддръжка.
По подразбиране wait_timeout
в MySQL е 8 часа. Всеки път, когато връзката е неактивна по-дълго от wait_timeout
затворено е. Когато MySQL сървърът се рестартира, той също така затваря всички установени връзки. По този начин, ако използвате постоянна връзка, трябва да проверите, преди да използвате връзка, дали тя е жива (и ако не, свържете се отново). Ако използвате връзка по заявка, не е необходимо да поддържате състоянието на връзката, тъй като връзката е винаги свежа.
Връзка на заявка
Непостоянната връзка с база данни има очевидни излишни разходи за отваряне на връзка, ръкостискане и т.н. (както за сървър на база данни, така и за клиент) за всяка входяща HTTP заявка.
Ето цитат от официалния урок на Flask относно връзките към базата данни :
Имайте предвид обаче, че контекстът на приложението се инициализира при заявка (което е някак завоалирано от опасенията за ефективност и жаргона на 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
за връзки с картографирани нишки.
Едно важно предупреждение при поддържането на постоянна връзка са транзакциите. API за повторно свързване е ping
. Безопасно е за автоматично записване на единични оператори, но може да наруши между транзакциите (малко повече подробности тук
). DBUtils се грижи за това и трябва да се свърже отново само при dbapi.OperationalError
и dbapi.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
Наблюдения (за моята локална конфигурация):
- Постоянната връзка е с ~30% по-бърза,
- При паралелност 4 и по-висока, работният процес на uWSGI достига пикове при над 100% използване на процесора (
pymysql
трябва да анализира MySQL протокола в чист Python, което е тесното място), - При паралелност 16,
mysqld
Използването на процесора е ~55% за заявка и ~45% за постоянна връзка.