You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

查询执行过慢优化求助:6k+用户数据加载耗时超1分钟

优化6k+用户查询性能的实战方案

哇,6k+用户查询耗时近一分钟确实够闹心的,咱们从查询逻辑、索引、代码细节三个层面来拆解优化,一步步把速度提上来!

一、先抓核心瓶颈:优化查询逻辑与聚合方式

你的查询里有多个LEFT JOIN和聚合函数,很容易产生笛卡尔积(比如一个用户对应多条视频记录,Join后数据量会爆炸式增长),这是性能慢的核心原因之一。

1. 把聚合查询从JOIN改成子查询,避免数据膨胀

原来的写法是Join整个btq_user_track_blog_video和event_data表再聚合,会生成大量中间数据。换成子查询单独计算每个用户的统计值,能大幅减少数据传输量:

// 替换原来的total_video_viewed列和对应的JOIN
$videoSubQuery = BtqUserTrackBlogVideoQuery::create()
    ->select(array('UserId', 'COUNT(Counter) as total_video_viewed'))
    ->groupBy('UserId')
    ->getSql();
$criteria->addSelectColumn("(SELECT total_video_viewed FROM ($videoSubQuery) t WHERE t.user_id = btq_user.id) as total_video_viewed");

// 替换event_data的两个统计列和对应的JOIN
$eventVideoSubQuery = EventDataQuery::create()
    ->select(array('BtqUserId', 'COUNT(Id) as kms_total_video_viewed'))
    ->filterByEventParentId(2)
    ->groupBy('BtqUserId')
    ->getSql();
$criteria->addSelectColumn("(SELECT kms_total_video_viewed FROM ($eventVideoSubQuery) t WHERE t.btq_user_id = btq_user.id) as kms_total_video_viewed");

$eventBlogSubQuery = EventDataQuery::create()
    ->select(array('BtqUserId', 'COUNT(Id) as kms_total_blog_viewed'))
    ->filterByEventParentId(1)
    ->groupBy('BtqUserId')
    ->getSql();
$criteria->addSelectColumn("(SELECT kms_total_blog_viewed FROM ($eventBlogSubQuery) t WHERE t.btq_user_id = btq_user.id) as kms_total_blog_viewed");

// 记得删掉原来对应的LEFT JOIN语句:
// $criteria->addJoin(self::ID, BtqUserTrackBlogVideoPeer::USER_ID, Criteria::LEFT_JOIN);
// $criteria->addJoin(self::ID, EventDataPeer::BTQ_USER_ID, Criteria::LEFT_JOIN);

2. 修正GROUP BY的逻辑,用唯一键代替EMAIL

你现在GROUP BY btq_user.email,但EMAIL可能重复(比如同一邮箱注册多个账号),而且GROUP BY非唯一键的聚合效率远低于主键/唯一键。改成GROUP BY用户ID:

// 替换原来的GROUP BY EMAIL
$criteria->addGroupByColumn(self::ID);
// 删掉$criteria->addGroupByColumn(self::EMAIL);

3. 按需加载JOIN表,避免不必要的关联

如果模板里不是所有场景都需要BtqUserSalesChoice、LeadSchedule这些表的数据,可以根据搜索参数判断是否添加JOIN:

// 比如只有当需要显示销售选择数据时才JOIN
if (isset($search_params['choice_type']) || /* 其他需要该表的条件 */) {
    $criteria->addJoin(self::BTQ_USER_SALES_CHOICE_ID, BtqUserSalesChoicePeer::ID, Criteria::LEFT_JOIN);
    // 同时添加对应的SELECT列
    $criteria->addSelectColumn("btq_user_sales_choice.type as choice_type");
    $criteria->addSelectColumn("btq_user_sales_choice.opt_value as choice_value");
    $criteria->addSelectColumn("btq_user_sales_choice.opt_text as choice_text");
}

二、给数据库加索引,让查询飞起来

没有合适的索引,数据库会做全表扫描,6k+数据量全表扫描自然慢。针对你的查询,必须加这些索引:

表名索引字段组合说明
btq_userdatain, is_dummy_detail用于日期过滤、排序和排除dummy数据
btq_userid(主键,应该已经有了)子查询关联、GROUP BY的核心索引
btq_useremail如果必须保留GROUP BY EMAIL的话需要加
btq_user_track_blog_videouser_id子查询统计视频观看数的关联索引
event_databtq_user_id, event_parent_id子查询统计视频/博客观看数的联合索引
btq_userstate_id, drid, btq_user_sales_choice_id各个LEFT JOIN的外键索引

比如给event_data加联合索引的SQL:

CREATE INDEX idx_event_data_user_parent ON event_data(btq_user_id, event_parent_id);

三、代码细节优化,减少不必要的开销

1. 去掉手动的addslashes,用Propel的参数绑定

Propel的Criteria会自动处理参数转义和绑定,手动加addslashes不仅多余,还可能导致SQL错误或注入风险:

// 删掉这行:$param = addslashes($param);

2. 分页处理,避免一次性加载所有数据

如果模板里是一次性渲染6k+用户,不仅查询慢,前端渲染也会卡。改成分页查询,比如每次查100条:

// 在函数末尾添加分页逻辑
$page = isset($search_params['page']) ? (int)$search_params['page'] : 1;
$perPage = 100;
$criteria->setOffset(($page - 1) * $perPage);
$criteria->setLimit($perPage);

3. 只SELECT需要的字段

检查模板里是否真的用到了你SELECT的所有列,比如pfu_customer_id、schedule_id这些如果没用到,就删掉对应的addSelectColumn,减少数据传输量。

四、最后一步:用EXPLAIN分析执行计划

不管做了多少优化,都要通过EXPLAIN看数据库实际怎么执行查询:

EXPLAIN [你的查询SQL];

重点看这几个指标:

  • type列:最好是range或ref,如果是ALL说明全表扫描,要调整索引
  • key列:有没有用到你加的索引
  • rows列:预估扫描的行数,如果远大于实际返回行数,说明索引有问题

内容的提问来源于stack exchange,提问作者Dev Troubleshooter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:46:42