千万级数据关联查询会卡死,求调优方法

业务代码

  $list = AdminUserList::with([
                'adminGroupList'=>function ( $query) use ($groupLevel) {
                    $query->where('group_level', '<', $groupLevel);
                }
            ]);

            if(!empty($params['login_time'])){
                $list = $list->with([
                    'baseLog'=>function($query)use($params){
                    $query->where([
                        ['log_type','=',1],
                        ['log_create_time','>',intval($params['login_time'])],
                        ['log_create_time','<',strtotime('+1 day',intval($params['login_time']))]
                    ]);
                    }
                ])->whereHas('baseLog',function (Builder  $query)use($params){
                    $query->where([
                        ['log_type','=',1],
                        ['log_create_time','>',intval($params['login_time'])],
                        ['log_create_time','<',strtotime('+1 day',intval($params['login_time']))]
                    ]);
                    }
                );
            }
            if(!empty($params['name']))$list=$list->where('nickname','like',"%{$params['name']}%");
            if(!empty($params['username']))$list=$list->where('username','like',"%{$params['username']}%");
            if (!empty($params['group_id']))$list=$list->where('group_id',$params['group_id']);

            $list = $list->where('id','<>',$uid)->select($select)->orderBy('id', 'desc')->paginate(perPage: $params['limit'], page: $params['page']);
            $count = $list->total();
            $list = $list->toArray()['data'];

求调优方法,还是我这种写法有问题?用户数据大概200万,日志数据有好几千万,

《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

1、上述涉及到 where 的字段都加到一个联合索引里面

2、<> 改为 not in

3、%{$params['username']}% 涉及到 like 查询,改为右匹配 {$params['username']}%

4、我记得社区有提过关于whereHas的性能问题,社区中搜索 whereHasIn 这个包。或者尝试使用 join 查询

5、数据量大的时候,分页也可能造成慢查询,建议 可以再加一个条件 id > $nextId,或者百度一下

1年前 评论
Frees (楼主) 1年前
讨论数量: 32

1、上述涉及到 where 的字段都加到一个联合索引里面

2、<> 改为 not in

3、%{$params['username']}% 涉及到 like 查询,改为右匹配 {$params['username']}%

4、我记得社区有提过关于whereHas的性能问题,社区中搜索 whereHasIn 这个包。或者尝试使用 join 查询

5、数据量大的时候,分页也可能造成慢查询,建议 可以再加一个条件 id > $nextId,或者百度一下

1年前 评论
Frees (楼主) 1年前

用like关键字就很难优化; 建议你开启一下debugbar 看一下sql是否可以优化

1年前 评论
Frees (楼主) 1年前

按照你这个带有分页,不应该出现很卡, 唯一值得考虑的是你的baselog 表很大,索引看看命中没

1年前 评论
Frees (楼主) 1年前
raybon (作者) 1年前
Frees (楼主) 1年前

记录下所有的sql语句 看下sql实际执行的情况

1年前 评论

1、上述涉及到 where 的字段都加到一个联合索引里面

2、<> 改为 not in

3、%{$params['username']}% 涉及到 like 查询,改为右匹配 {$params['username']}%

4、我记得社区有提过关于whereHas的性能问题,社区中搜索 whereHasIn 这个包。或者尝试使用 join 查询

5、数据量大的时候,分页也可能造成慢查询,建议 可以再加一个条件 id > $nextId,或者百度一下

1年前 评论
Frees (楼主) 1年前

AdminUser 添加字段 login_time 字段能解决你的问题

1年前 评论

这写法看着好难受啊

1年前 评论
Frees (楼主) 1年前

先不说别的,你可以试着用when方法优化下代码。那么多的 if 看着真难受。

1年前 评论
Frees (楼主) 1年前

join 加索引应该会好点哦

1年前 评论

->whereHas('baseLog',function (Builder $query)use($params){ $query->where([ ['log_type','=',1], ['log_create_time','>',intval($params['login_time'])], ['log_create_time','<',strtotime('+1 day',intval($params['login_time']))] ]); } ) 这个where哈希数据量多贼慢 ,你可以打印sql 分析一下

1年前 评论
Frees (楼主) 1年前

问题肯定在baseLog这边 从这边入手 不建议连表

1年前 评论
寞小陌 (作者) 1年前
Frees (楼主) 1年前
寞小陌 (作者) 1年前
sanders

简单说下:管理员外键做索引,如果管理员较多(如:上万)那么建议按管理员做哈希分表或分区。如果时间是必填参数且周期固定,建议按照时间做分表或分区。前面两个分表分区条件可差异组合使用来尽量降低匹配的分区或分表数量。

1年前 评论

whereHas 改成 whereHasIn , whereHas 用的是 exists 走不了索引

1年前 评论
jiangjun

1.上千万的查询不建议连表,建议代码逻辑组装 2.所有查询必须命中索引 3.like这种查询放到es中

1年前 评论

有3 个表,admin_usersadmin_user_groupsbase_logs

应该是查询 某人的日志

  1. base_logs 添加 user group id 字段
  2. base_logs 创建必要的索引

基于 base_logs 表查询

-- 未验证,只做演示用
select log.*, u.username, g.group_name
from base_logs log
  left join admin_usersu ON ..
  left join admin_user_groups g ON ..
where log.user_id in (select id from admin_users where username like '%xxx%' /* or nickname like '%xxx%' */)
   AND `log_create_time` between  xxxx and xxx2
  and `log_type` = 1
  -- and log.user_group_id = 123
limit 100
1年前 评论
Frees (楼主) 1年前
Laravel00

针对大规模数据的关联查询,可以采取以下一些优化措施来提高性能:

  1. 优化数据库索引: 确保相关的数据库字段都有适当的索引,以加快查询速度。

  2. 懒加载关联关系: 不是每次都需要加载关联数据,可以使用懒加载来按需加载,避免一次性加载大量数据。

  3. 分批处理: 如果数据量非常大,可以使用 chunk 方法来分批处理,避免一次性加载全部数据到内存。

  4. 使用原生 SQL: 在某些情况下,使用原生 SQL 查询可能比 Eloquent 查询更高效。

  5. 避免 N + 1 查询问题: 使用 with 方法加载关联数据可以避免 N + 1 查询问题,但确保只加载必要的关联数据。

  6. 使用查询生成器: 使用查询生成器而不是 Eloquent ORM 可以提高性能,因为查询生成器生成的 SQL 更精细。

  7. 使用缓存: 如果查询结果不经常变化,可以考虑使用缓存来减轻数据库负担。

  8. 合并多个查询: 尝试合并多个查询,减少数据库连接次数。

  9. 避免复杂的嵌套查询: 复杂的嵌套查询可能会导致性能下降。

  10. 使用延迟加载关联: 如果关联数据不是每次都需要的,可以使用延迟加载来避免加载不必要的数据。

针对你的代码,以下是一个可能的优化方案:

$list = AdminUserList::with(['adminGroupList' => function ($query) use ($groupLevel) {
    $query->where('group_level', '<', $groupLevel);
}]);

if (!empty($params['login_time'])) {
    // 不再立即加载 baseLog,而是将条件添加到 with 里
    $list = $list->with(['baseLog' => function ($query) use ($params) {
        $query->where([
            ['log_type', '=', 1],
            ['log_create_time', '>', intval($params['login_time'])],
            ['log_create_time', '<', strtotime('+1 day', intval($params['login_time']))],
        ]);
    }]);
}

if (!empty($params['name'])) {
    $list = $list->where('nickname', 'like', "%{$params['name']}%");
}

if (!empty($params['username'])) {
    $list = $list->where('username', 'like', "%{$params['username']}%");
}

if (!empty($params['group_id'])) {
    $list = $list->where('group_id', $params['group_id']);
}

$list = $list->where('id', '<>', $uid)
    ->select($select)
    ->orderBy('id', 'desc')
    ->paginate(perPage: $params['limit'], page: $params['page']);

// 加载关联数据,并将数据转换为数组
$list = $list->load('adminGroupList', 'baseLog')->toArray()['data'];
$count = $list->total();

以上是一个简单的优化示例,实际的优化策略可能会根据数据库结构、数据量和查询频率的不同而有所变化。如果你的数据量非常大,还可以考虑使用分批处理等更高级的优化策略。

1年前 评论

取消数据关联,创建集合表,需要的数据从集合表中查询

1年前 评论
Frees (楼主) 1年前

不用关联了,单表查都会卡死

1年前 评论

先看索引命中,然后加联合索引,不过你最好还是给表做分区,不然表数据到达mysql的峰值,再优化也没卵用。另外为什么不把这个业务丢到异步任务里呢?还能避免阻塞主线程。 撇开数据库优化,你这代码的优化空间可太大了 :joy:

group_levellog_typelog_create_timenicknameusernamegroup_id这些where条件应该创建索引。在where 子查询中,可以使用 BETWEEN替代多个条件的组合

未验证
$list = AdminUserList::query()
    ->leftJoin('admin_group_lists', 'admin_user_lists.id', '=', 'admin_group_lists.user_id')
    ->when(!empty($params['login_time']), function ($query) use ($params) {
        $query->leftJoin('base_logs', 'admin_user_lists.id', '=', 'base_logs.user_id')
            ->where('base_logs.log_type', 1)
            ->where('base_logs.log_create_time', '>', intval($params['login_time']))
            ->where('base_logs.log_create_time', '<', strtotime('+1 day', intval($params['login_time'])));
    })
    ->when(!empty($params['name']), function ($query) use ($params) {
        $query->where('admin_user_lists.nickname', 'like', "%{$params['name']}%");
    })
    ->when(!empty($params['username']), function ($query) use ($params) {
        $query->where('admin_user_lists.username', 'like', "%{$params['username']}%");
    })
    ->when(!empty($params['group_id']), function ($query) use ($params) {
        $query->where('admin_user_lists.group_id', $params['group_id']);
    })
    ->where('admin_user_lists.id', '<>', $uid)
    ->select($select)
    ->orderBy('admin_user_lists.id', 'desc')
    ->simplePaginate($params['limit'], ['*'], 'page', $params['page']);

$count = $list->total();
$list = $list->toArray()['data'];
1年前 评论
xiaoguo0426 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!