记录影响性能的缓慢数据库查询
如果程序性能随着时间推移不断降低,那很有可能是因为数据库查询变慢了,随着数据库规模的增长,这一情况还会变得更糟,优化数据库有时很简单,只需添加更多的索引即可,有时也很复杂,需要在程序和数据库之前加入缓存,大多数数据库查询语言都提供了explain
语句,用来显示数据库执行查询时采取的步骤,从这些步骤中,我们经常能发现数据库或索引设计的不足之处
不过,在开始优化查询之前,我们必须要知道哪些查询是值得优化的,在一次典型请求中,可能要执行多条数据库查询,所以经常很难分辨哪一条查询较慢,Flask-SQLAlchemy提供了一个选项,可以记录请求中执行的与数据库查询相关的统计数字,在下例中,我们可以看到如何使用这个功能把慢于设定阈值的查询写入日志
# app/main/views.py
from flasky_sqkalchemy import get_debug_queries
@main.after_app_request
def after_request(response):
for query in get_debug_queries():
if query.duration >= current_app.config['FLASKY_SLOW_DB_QUERY_TIME']:
current_app.logger.warning(
'Slow query: %s\nParameters: %s\nDuration: %fs\nContext: %s\n' (query.statement, query.parameters, query.duration,
query.context))
return response
这个功能使用after_app_request
处理程序实现,它和before_app_request
处理程序的工作方式类似,只不过在视图函数处理完请求之后执行,Flask把响应对象传给after_app_request
处理程序,以防需要修改响应
在本例中,after_app_request
处理程序没有修改响应,只是获取Flask-SQLAlchemy记录的查询时间并把缓慢的查询写入日志
get_debug_queries()
函数返回一个列表,其元素是请求中执行的查询,查询信息如下表:
名称 | 说明 |
---|---|
statement | SQL语句 |
parameters | SQL语句使用的参数 |
start_time | 执行查询时的时间 |
end_time | 返回查询结果时的时间 |
duration | 返回持续的时间,单位为秒 |
context | 表示查询在源码中所处位置的字符串 |
after_app_request
处理程序遍历get_debus_queries()
函数获取的列表,把持续时间比设定阈值长的查询写入日志,写入的日志被设为“警告”等级,如果换成“错误”等级,发现缓慢的查询时还会发送电子邮件
默认情况下,get_debug_queries()函数只在调试模式中可用,但是数据库性能问题很少发生在开发阶段,因为开发过程中使用的数据库较小,因此,在生产环境中使用该选项才能发挥作用,若想在生产环境中分析数据库性能,我们必须修改配置,如下:
class Config:
#...
SQLALCHEMY_RECORD_QUERIES = True
FLASKY_DB_QUERY_TIMEOUT = 0.5
#...
SQLALCHEMY_RECORD_QUERIES
告诉Flask-SQLAlchemy启用记录查询统计数字的功能,缓慢查询的阈值设为0.5秒,这两个配置变量都在Config
基类中设置,因此在所有环境中都可使用
每当发现缓慢查询,Flask程序的日志记录器就会写入一条记录,若想保存这些日志记录,必须配置日志记录器,日志记录器的配置根据程序所在主机使用的平台而有所不同
分析源码
性能问题的另一个可能诱因是高CPU消耗,由执行大量运算的函数导致,源码分析器能找出程序中执行最慢的部分,分析器监视运行中的程序,记录调用的函数以及运行各函数所消耗的时间,然后生成一份详细的报告,指出运行最慢的函数
分析一般在开发环境中进行,源码分析器会让程序运行得更慢,因为分析器要监视并记录程序中发生的一切,因此不建议在生产环境中进行分析,除非使用专为生产环境设计的轻量级分析器
Flask使用的开发Web服务器由Werkzeug提供,可根据需要为每条请求启用Python分析器,下例向程序中添加了一个新命令,用来启动分析器
# /manage.py
@manager.command
def profile(length=25, profile_dir=None):
"""Start the application under the code profile"""
from werkzeug.contrib.profiler import ProfileMiddleware
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[length],
profile_dir=profile_dir)
app.run()
使用python manage.py profile
启动程序后,终端会显示每条请求的分析数据,其中包含运行最慢的25个函数, --length
选项可以修改报告中显示的函数数量,如果指定了 --profile -dir
选项,每条请求的分析数据就会保存到指定目录下的一个文件中,分析器数据文件可用来生成更详细的报告,利用调用图