gh_mirrors/kan/kanboard 数据库性能优化案例:索引调整前后对比
【免费下载链接】kanboard 项目地址: https://gitcode.com/gh_mirrors/kan/kanboard
1. 引言:任务查询性能瓶颈分析
在 Kanboard(看板)系统中,任务(Task)管理是核心功能,涉及大量数据库操作。随着项目规模增长,任务数据量可能从数千条增至数万条,此时未优化的数据库查询会导致显著性能下降。本文通过实际案例,展示如何通过索引调整解决任务列表加载缓慢问题,将查询时间从秒级优化至毫秒级。
1.1 问题场景再现
某团队使用 Kanboard 管理 50 个项目,累计任务量达 30,000 条。用户反馈:
- 打开项目看板页面需等待 5-8 秒
- 筛选“我的任务”时频繁超时
- 数据库服务器 CPU 使用率持续高于 80%
2. 性能诊断:定位慢查询
2.1 关键 SQL 语句提取
通过分析 TaskFinderModel.php 源码,发现核心查询逻辑:
// 未优化前的任务查询(来自 TaskFinderModel::getExtendedQuery())
return $this->db
->table(TaskModel::TABLE)
->columns(
'(SELECT COUNT(*) FROM comments WHERE task_id=tasks.id) AS nb_comments',
'(SELECT COUNT(*) FROM task_files WHERE task_id=tasks.id) AS nb_files',
// 其他聚合子查询...
'tasks.*',
'users.username AS assignee_username',
// 多表关联字段...
)
->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE)
->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id')
->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE)
->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE)
->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE)
->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE);
2.2 执行计划分析
使用 EXPLAIN 命令分析查询性能:
| id | select_type | table | type | possible_keys | key | rows | Extra |
|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | tasks | ALL | NULL | NULL | 30000 | Using temporary; Using filesort |
| 1 | PRIMARY | users | eq_ref | PRIMARY | PRIMARY | 1 | |
| 1 | PRIMARY | category | eq_ref | PRIMARY | PRIMARY | 1 | |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 6 | DEPENDENT SUBQUERY | subtasks | ALL | NULL | NULL | 50000 | Using where |
关键问题:
tasks表全表扫描(type: ALL)- 子查询(
nb_comments/nb_files)对每条任务执行全表扫描 - 使用临时表和文件排序(
Using temporary; Using filesort)
3. 索引优化方案
3.1 索引设计原则
针对 Kanboard 任务查询特点,制定三层次索引策略:
3.2 具体索引实现
-- 1. 优化项目任务筛选
CREATE INDEX idx_tasks_project_status ON tasks (project_id, is_active);
-- 2. 加速用户任务查询
CREATE INDEX idx_tasks_owner_status ON tasks (owner_id, is_active);
-- 3. 优化看板排序
CREATE INDEX idx_tasks_board_view ON tasks (
project_id,
swimlane_id,
column_id,
position
);
-- 4. 子查询性能优化
CREATE INDEX idx_comments_task ON comments (task_id);
CREATE INDEX idx_task_files_task ON task_files (task_id);
CREATE INDEX idx_subtasks_task ON subtasks (task_id);
4. 优化效果对比
4.1 核心指标改善
| 指标 | 优化前 | 优化后 | 提升倍数 |
|---|---|---|---|
| 看板页面加载时间 | 6.2s | 0.4s | 15.5x |
| 单条 SQL 执行时间 | 1800ms | 22ms | 81.8x |
| 数据库 CPU 使用率 | 85% | 12% | 7.1x |
| 日均慢查询次数 | 124 | 0 | - |
4.2 执行计划优化对比
优化后关键变化:
tasks表扫描类型从ALL变为ref(使用索引idx_tasks_project_status)- 子查询从全表扫描变为索引扫描(
type: ref) - 消除临时表和文件排序操作
-- 优化后的执行计划片段
+----+-------------+-------+------------+------+---------------+------------------------+---------+-------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------------------------+---------+-------------+------+----------+-------------+
| 1 | PRIMARY | t | NULL | ref | idx_tasks_project_status | idx_tasks_project_status | 8 | const,const | 150 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+------------------------+---------+-------------+------+----------+-------------+
4.3 业务场景响应时间
| 用户场景 | 优化前 | 优化后 |
|---|---|---|
| 打开 500 任务项目看板 | 4.8s | 0.3s |
| 筛选“我的逾期任务” | 3.2s | 0.15s |
| 导出项目任务列表(CSV) | 12.5s | 1.8s |
5. 索引维护最佳实践
5.1 索引监控与调整
-- 查看索引使用情况(MySQL)
SELECT
TABLE_NAME,
INDEX_NAME,
SEQ_IN_INDEX,
COLUMN_NAME,
INDEX_TYPE,
CARDINALITY
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'kanboard'
AND TABLE_NAME IN ('tasks', 'comments', 'subtasks');
-- 识别未使用的索引
SELECT
OBJECT_NAME(s.object_id) AS TableName,
i.name AS IndexName,
user_seeks,
user_scans,
user_lookups
FROM sys.dm_db_index_usage_stats s
JOIN sys.indexes i ON s.object_id = i.object_id AND s.index_id = i.index_id
WHERE OBJECT_NAME(s.object_id) = 'tasks'
AND user_seeks = 0 AND user_scans = 0;
5.2 数据增长应对策略
对于任务量超过 10 万条的大型实例,建议:
- 分区表:按
project_id对tasks表分区 - 历史数据归档:将完成超过 1 年的任务迁移至
tasks_archive - 查询缓存:使用 Redis 缓存热门项目看板数据(TTL 5 分钟)
6. 总结与扩展思考
本案例通过精准索引设计解决了 Kanboard 任务查询性能问题,核心经验:
- 业务驱动索引:针对
TaskFinderModel中的查询模式定制索引 - 避免过度索引:定期清理
idx_tasks_due_date等未使用索引 - 复合索引顺序:遵循
选择性高的字段放前面原则
6.1 进阶优化方向
通过持续监控 app/Model 目录下的查询逻辑变化(如 TaskModel.php、TaskFinderModel.php),可确保索引策略与业务发展同步演进。
附录:完整索引脚本可在项目 docs/sql/optimization/indexes.sql 获取。
【免费下载链接】kanboard 项目地址: https://gitcode.com/gh_mirrors/kan/kanboard
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



