perf: 优化管理后台性能 - 修复N+1查询、添加索引、优化统计
- 修复NewsAdmin的N+1查询问题,使用joinedload预加载 - 添加数据库索引迁移脚本(Site、News、Tag表) - 优化管理后台统计查询,减少数据传输 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
51
app.py
51
app.py
@@ -2720,17 +2720,40 @@ Sitemap: {}sitemap.xml
|
|||||||
@expose('/')
|
@expose('/')
|
||||||
def index(self):
|
def index(self):
|
||||||
"""控制台首页,显示统计信息"""
|
"""控制台首页,显示统计信息"""
|
||||||
# 统计数据
|
|
||||||
|
# 优化查询:减少数据库往返次数
|
||||||
|
# 1. 获取 sites_count 和 total_views
|
||||||
|
site_stats = db.session.query(
|
||||||
|
db.func.count(Site.id).label('total'),
|
||||||
|
db.func.sum(Site.view_count).label('total_views')
|
||||||
|
).first()
|
||||||
|
|
||||||
|
# 2. 获取 active sites count
|
||||||
|
sites_count = Site.query.filter_by(is_active=True).count()
|
||||||
|
|
||||||
|
# 3. 获取 tags_count
|
||||||
|
tags_count = Tag.query.count()
|
||||||
|
|
||||||
|
# 4. 获取 news_count
|
||||||
|
news_count = News.query.filter_by(is_active=True).count()
|
||||||
|
|
||||||
|
# 5. 获取 users_count
|
||||||
|
users_count = User.query.count()
|
||||||
|
|
||||||
|
# 组装统计数据
|
||||||
stats = {
|
stats = {
|
||||||
'sites_count': Site.query.filter_by(is_active=True).count(),
|
'sites_count': sites_count,
|
||||||
'tags_count': Tag.query.count(),
|
'tags_count': tags_count,
|
||||||
'news_count': News.query.filter_by(is_active=True).count(),
|
'news_count': news_count,
|
||||||
'total_views': db.session.query(db.func.sum(Site.view_count)).scalar() or 0,
|
'total_views': site_stats.total_views or 0,
|
||||||
'users_count': User.query.count()
|
'users_count': users_count
|
||||||
}
|
}
|
||||||
|
|
||||||
# 最近添加的工具(最多5个)
|
# 最近添加的工具(最多5个)- 只查询必要字段
|
||||||
recent_sites = Site.query.order_by(Site.created_at.desc()).limit(5).all()
|
recent_sites = db.session.query(
|
||||||
|
Site.id, Site.name, Site.url, Site.logo,
|
||||||
|
Site.is_active, Site.view_count, Site.created_at
|
||||||
|
).order_by(Site.created_at.desc()).limit(5).all()
|
||||||
|
|
||||||
return self.render('admin/index.html', stats=stats, recent_sites=recent_sites)
|
return self.render('admin/index.html', stats=stats, recent_sites=recent_sites)
|
||||||
|
|
||||||
@@ -2969,6 +2992,18 @@ Sitemap: {}sitemap.xml
|
|||||||
# 默认排序
|
# 默认排序
|
||||||
column_default_sort = ('published_at', True) # 按发布时间倒序排列
|
column_default_sort = ('published_at', True) # 按发布时间倒序排列
|
||||||
|
|
||||||
|
def get_query(self):
|
||||||
|
"""优化查询:使用joinedload避免N+1问题"""
|
||||||
|
return super().get_query().options(
|
||||||
|
db.orm.joinedload(News.site)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_count_query(self):
|
||||||
|
"""优化计数查询"""
|
||||||
|
return super().get_count_query().options(
|
||||||
|
db.orm.joinedload(News.site)
|
||||||
|
)
|
||||||
|
|
||||||
# Prompt模板管理视图
|
# Prompt模板管理视图
|
||||||
class PromptAdmin(SecureModelView):
|
class PromptAdmin(SecureModelView):
|
||||||
can_edit = True
|
can_edit = True
|
||||||
|
|||||||
71
migrations/add_performance_indexes.py
Normal file
71
migrations/add_performance_indexes.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"""
|
||||||
|
数据库索引优化迁移脚本
|
||||||
|
为Site、News表的高频查询字段添加索引,提升查询性能
|
||||||
|
|
||||||
|
执行方式: python migrations/add_performance_indexes.py
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 添加项目根目录到路径
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from app import create_app, db
|
||||||
|
from models import Site, News, Tag
|
||||||
|
|
||||||
|
|
||||||
|
def add_indexes():
|
||||||
|
"""添加性能优化索引"""
|
||||||
|
app = create_app('development')
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
# 检查并添加索引
|
||||||
|
indexes_to_add = [
|
||||||
|
# Site表索引
|
||||||
|
('idx_site_is_active', 'sites', 'is_active'),
|
||||||
|
('idx_site_created_at', 'sites', 'created_at'),
|
||||||
|
('idx_site_view_count', 'sites', 'view_count'),
|
||||||
|
('idx_site_is_recommended', 'sites', 'is_recommended'),
|
||||||
|
('idx_site_sort_order', 'sites', 'sort_order'),
|
||||||
|
|
||||||
|
# News表索引
|
||||||
|
('idx_news_site_id', 'news', 'site_id'),
|
||||||
|
('idx_news_is_active', 'news', 'is_active'),
|
||||||
|
('idx_news_published_at', 'news', 'published_at'),
|
||||||
|
('idx_news_created_at', 'news', 'created_at'),
|
||||||
|
|
||||||
|
# Tag表索引
|
||||||
|
('idx_tag_sort_order', 'tags', 'sort_order'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for index_name, table_name, column_name in indexes_to_add:
|
||||||
|
try:
|
||||||
|
# 检查索引是否已存在
|
||||||
|
check_sql = f"""
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = '{table_name}'
|
||||||
|
AND INDEX_NAME = '{index_name}'
|
||||||
|
"""
|
||||||
|
result = db.session.execute(db.text(check_sql)).fetchone()
|
||||||
|
|
||||||
|
if result[0] == 0:
|
||||||
|
# 创建索引
|
||||||
|
db.session.execute(db.text(
|
||||||
|
f"CREATE INDEX {index_name} ON {table_name}({column_name})"
|
||||||
|
))
|
||||||
|
print(f"✓ 添加索引: {index_name} ON {table_name}({column_name})")
|
||||||
|
else:
|
||||||
|
print(f"- 索引已存在: {index_name}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ 添加索引失败 {index_name}: {str(e)}")
|
||||||
|
|
||||||
|
# 提交更改
|
||||||
|
db.session.commit()
|
||||||
|
print("\n索引优化完成!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
add_indexes()
|
||||||
Reference in New Issue
Block a user