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

Laravel Yajra Datatables加载超1万条数据缓慢的优化咨询

万级数据Yajra Datatables加载慢优化方案

1. 核心问题修复:开启服务端渲染模式

当前最大的性能损耗是关闭了服务端分页,前端一次拉取全量1万条数据,且控制器提前查询全量数据转数组,完全浪费了Yajra Datatables的服务端处理能力。

修改前端代码

取消serverSide的注释:

$('.playertable').DataTable({
    "language": {
        "url": "//cdn.datatables.net/plug-ins/9dcbecd42ad/i18n/"+lag
    },
    processing: true,
    serverSide: true, // 取消注释开启服务端模式
    responsive: true,
    searching:  true,
    "order": [[ 5, "desc" ]],
    ajax: {
        url: "{{ route('getplayerlist') }}",
        type: 'GET',
        data: function (d) {
            // 自定义搜索参数可在这里补充
        }
    },
    columns: [
            {data: 'games', name: 'games'},
            {data: 'player_no', name: 'username'},
            {data: 'game_username', name: 'gameid'},
            {data: 'balance', name: 'balance'},
            {data: 'phone_number', name: 'phone'},
            {data: 'created_at', name: 'createdate'},
            {data: 'status', name: 'status'},
            {data: 'action', name: 'action', orderable: false, searchable: false, 'className': "dt-center"},
        ]
});

修改控制器代码

不要提前执行get()查询全量数据,直接把查询构造器传给Datatables::of():

public function getPlayerlistAction(Request $request)
{
    if ($request->ajax()) {
        // 直接传QueryBuilder,不要执行get()获取全量数据
        $playerQuery = Player::where('parent_id', Auth::user()->id)
                    ->with(['getGames' => function ($q){
                        $q->select('id', 'title', 'display_name');
                    }])
                    ->orderBy('created_at', 'desc');

        return Datatables::of($playerQuery)
            ->addColumn('games', function ($row) {
                return $row->getGames->display_name;
            })
            ->addColumn('phone_number', function ($row) {
                return $row->phone_number == '000' ? '-' : $row->phone_number;
            })
            ->addColumn('balance', function ($row) {
                return number_format($row->balance, 2);
            })
            ->addColumn('status', function ($row) {
                if ($row->status == 1) {
                    return '<span class="badge badge-pill badge-success">' . __('mainlanguage.enabledtxt') . '</span>';
                } 
                return '<span class="badge badge-pill badge-danger">' . __('mainlanguage.disabledtxt') .'</span>';
            })
            ->addColumn('action', function ($row) {
               $actionBtn  = '<span><button class="btn btn-primary btn-sm mt-1 mr-1 setscrore" data-gameusername="' . $row->game_username . '" data-game="' . $row->getGames->title . '" data-username="' . $row->player_no . '" data-gid="' . $row->games_id . '"><i class="fa fa-credit-card"></i> ' . __('mainlanguage.setscoretxt') . '</button>';
               $actionBtn .= '<button class="btn bg-orange color-palette btn-sm mt-1 mr-1 resetpwd" data-gameusername="' . $row->game_username . '" data-gid="' . $row->games_id . '" data-username="'.$row->player_no.'" ><i class="fa fa-unlock"></i> ' . __('mainlanguage.resetpasstxt') . '</button>';
               if($row->status == 1){
                    $actionBtn .= '<button class="btn btn-danger btn-sm mt-1 mr-1 disable" datatype=0 data-gameusername="' . $row->game_username . '" data-gid="' . $row->games_id . '" data-username="' . $row->player_no . '"><i class="fa fa-ban"></i> ' . __('mainlanguage.disabletxt') . '</button>';
               }else{
                    $actionBtn .= '<button class="btn btn-success btn-sm mt-1 mr-1 disable" datatype=1 data-gameusername="' . $row->game_username . '" data-gid="' . $row->games_id . '" data-username="' . $row->player_no . '"><i class="fa fa-ban"></i> ' . __('mainlanguage.enabledtxt') . '</button>';
               }
               $actionBtn .= '<button class="btn bg-teal color-palette btn-sm mt-1 mr-1 gamelog" data-gamename="'.$row->getGames->title.'" data-gameusername="' . $row->game_username . '" data-gid="' . $row->games_id . '" data-username="' . $row->player_no .'"><i class="fa fa-scroll"></i> ' . __('mainlanguage.gamelogtxt') . '</button>';
               $actionBtn .= '<button class="btn bg-maroon color-palette btn-sm mt-1 mr-1 transactionlog" data-gameusername="' . $row->game_username . '" data-gid="' . $row->games_id . '" data-username="' . $row->player_no .'"><i class="fa fa-book"></i> ' . __('mainlanguage.tranactilogtxt') . '</button></span>';
               return $actionBtn;
            })
            ->rawColumns(['games', 'balance', 'status', 'action', 'phone'])
            ->make(true);
    }
}

修改后每次只会查询当前页的10/20条数据,接口响应速度可降至100ms以内。

2. 数据库索引优化

当前的查询条件是where parent_id = ? order by created_at desc,需要给players表加parent_id + created_at联合索引,单独加单字段索引不会生效:

ALTER TABLE `players` ADD INDEX `idx_parent_created` (`parent_id`, `created_at` DESC);

3. 进一步性能优化(可选)

  • 把字段格式化、HTML拼接逻辑移到前端处理,服务端只返回原始数据,减少服务端循环计算开销。比如balance格式化、status状态标签、操作按钮都可以在前端columns的render函数里生成。
  • 如果数据实时性要求不高,可以给查询结果加5-10分钟的缓存,相同用户重复请求不用重复查库。
  • 只查询需要的字段,在Player查询里加select方法,明确指定要返回的字段,避免查询无用大字段占用内存。

内容的提问来源于stack exchange,提问作者ventures 999

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 17:48:03