Laravel 数据表加载数据需要花费太多时间

Laravel datatable taking too much of time to load data

提问人:user3837868 提问时间:9/20/2021 最后编辑:user3837868 更新时间:2/9/2022 访问量:6518

问:

这里使用下面的代码加载订单数据,但加载时间太长,我也在使用 serverSide,只有 320 个数据,TTFB 非常高,我也尝试了“pageLength”:30,“stateSave”:true,并使用 Cloudflare 和 Nginx 服务器,但结果相同,请让我知道我如何解决这个问题。我尝试了大多数解决方案,但仍然有问题。

Controller.php

$orders = new Order;
        $search = $request->search['value'];
        $filter_type = $request->filter_type;
        if ($filter_type == "custom") {
            $from = date('Y-m-d' . ' 00:00:00', strtotime($request->from_dates));
            if ($request->has('to_dates')) {
                $to = date('Y-m-d' . ' 23:59:59', strtotime($request->to_dates));
                $orders = $orders->whereBetween('created_at', array($from, $to));
            }
        }
        elseif ($filter_type == "daily") {
            $orders = $orders->where('created_at', '>=', Carbon\Carbon::today());
        }
        elseif ($filter_type == "weekly") {
            $fromDate = Carbon\Carbon::now()->subDay()->startOfWeek()->toDateString();
            $tillDate = Carbon\Carbon::now()->subDay()->endOfWeek()->toDateString();
            $orders = $orders->whereBetween(DB::raw('date(created_at)'), [$fromDate, $tillDate]);
        }
        elseif ($filter_type == "monthly") {
            $orders = $orders->whereRaw('MONTH(created_at) = ?', [date('m')]);
        }
        elseif ($filter_type == "yearly") {
            $orders = $orders->whereRaw('YEAR(created_at) = ?', [date('Y')]);
        }
        $orders = $orders->orderByDesc('id')->select();
        $orders = $orders->get();

        $datatable = DataTables::of($orders)
            ->addColumn('id', function ($orders) {
                return @$orders->id;
            })
            ->addColumn('payment_type', function ($orders) {
                return @$orders->payment_type_text;
            })
            ->addColumn('user_name', function ($orders) {
                return @$orders->user->name;
            })
            ->addColumn('store_name', function ($orders) {
                return @$orders->store->name;
            })
            ->addColumn('service_type', function ($orders) {
                return @$orders->store->service_type1->service_name;
            })
            ->addColumn('total', function ($orders) {
                return html_entity_decode(currency_symbol() . (@$orders->total_amount+@$orders->wallet_amount));
            })
            ->addColumn('status_text', function ($orders) {
                return @$orders->status_text;
            })
            ->addColumn('action', function ($orders) {
                return '<a title="' . trans('admin_messages.view') . '" href="' . route('admin.view_order', $orders->id) . '" ><i class="material-icons">edit</i></a>';

            });
        $columns = ['id', 'payment_type', 'user_name', 'store_name', 'total', 'status_text'];

        $base = new DataTableBase($orders, $datatable, $columns, 'Orders');
        return $base->render(null);

Blade.php

@extends('admin/template')
@section('main')
<?php flush(); ?>
<div class="content" ng-controller="statements" ng-cloak>
        <div class="card">

<div class="card-header card-header-rose card-header-text">
                  <div class="card-text">
                    <h4 class="card-title">{{$form_name}}</h4>
                  </div>
                </div>
                  <div class="card-body ">
            <div class="table-responsive">
                <table id="statement_table" class="table table-condensed w-100">
                </table>
            </div>
        </div>
        </div>
    </div>
</div>
@endsection

@push('scripts')
<link rel="stylesheet" href="{{asset('admin_assets/css/buttons.dataTables.css')}}">
<script src="{{asset('admin_assets/js/dataTables.buttons.js')}}">
</script>
<script src={{url('vendor/datatables/buttons.server-side.js')}}></script>

<script>

    var column = [
    {data: 'id', name: 'id', title: '{{trans("admin_messages.order_id")}}' },
    {data: 'payment_type',name: 'payment_type',title: '{{trans("admin_messages.payment_type")}}',searchable: true},
    {data: 'user_name',name: 'user_name',title: '{{trans("admin_messages.user_name")}}'},
    {data: 'user_address',name: 'user_address',title: '{{trans("admin_messages.address")}}'},
    {data: 'mobile_number',name: 'mobile_number',title: '{{trans("admin_messages.mobile_number")}}'},
    {data: 'store_name',name: 'store_name',title: '{{trans("admin_messages.store_name")}}'},
    {data: 'service_type',name: 'service_type',title: '{{trans("admin_messages.service_type")}}'},
    {data: 'total',name: 'total',title: '{{trans("admin_messages.total")}}'},
    {data: 'status_text',name: 'status_text',title: '{{trans("admin_messages.order_status")}}'},
    {data: 'created_at',name: 'created_at',title: '{{trans("admin_messages.created_at")}}'},
    {data: 'action',name: 'action',title: '{{trans("admin_messages.action")}}',orderable: false,searchable: false}
    ];

  var oTable = $('#statement_table').DataTable({
    dom:"lBfrtip",
    buttons:["csv","excel","print"],
    order:[0, 'desc'],
    processing: true,
    serverSide: true,
    
    ajax: {
      url: ajax_url_list['all_orders'],
      data: function (d) {
        d.filter_type = $('#filter_by').val();
        d.from_dates = $('#from_date').val();
        d.to_dates = $('#to_date').val();
      }
    },
    columns: column
  });
</script>
@endpush

创建表格

CREATE TABLE `order` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `store_id` int(10) unsigned DEFAULT NULL,
  `user_id` int(10) unsigned DEFAULT NULL,
  `driver_id` int(11) DEFAULT NULL,
  `recipient` tinyint(4) DEFAULT NULL,
  `subtotal` decimal(11,2) DEFAULT NULL,
  `offer_percentage` decimal(11,2) DEFAULT NULL,
  `offer_amount` decimal(11,2) DEFAULT NULL,
  `promo_id` int(11) DEFAULT NULL,
  `promo_amount` decimal(11,2) DEFAULT NULL,
  `delivery_fee` decimal(11,2) DEFAULT NULL,
  `booking_fee` decimal(11,2) DEFAULT NULL,
  `store_commision_fee` decimal(11,2) DEFAULT NULL,
  `driver_commision_fee` decimal(11,2) DEFAULT NULL,
  `tax` decimal(11,2) DEFAULT NULL,
  `total_amount` decimal(11,2) DEFAULT NULL,
  `wallet_amount` decimal(11,2) DEFAULT NULL,
  `payment_type` tinyint(4) DEFAULT NULL,
  `owe_amount` decimal(11,2) DEFAULT NULL,
  `store_owe_amount` decimal(11,2) DEFAULT NULL,
  `applied_owe` decimal(11,2) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `payout_status` tinyint(4) DEFAULT NULL,
  `currency_code` varchar(3) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `est_preparation_time` time DEFAULT NULL,
  `est_travel_time` time DEFAULT NULL,
  `est_delivery_time` time DEFAULT NULL,
  `cancelled_by` tinyint(4) DEFAULT NULL,
  `cancelled_reason` int(10) unsigned DEFAULT NULL,
  `cancelled_message` text COLLATE utf8mb4_unicode_ci,
  `delay_min` time DEFAULT NULL,
  `delay_message` text COLLATE utf8mb4_unicode_ci,
  `schedule_status` tinyint(4) DEFAULT '0',
  `payout_is_create` tinyint(4) DEFAULT '0',
  `schedule_time` timestamp NULL DEFAULT NULL,
  `notes` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `user_notes` text COLLATE utf8mb4_unicode_ci,
  `store_notes` text COLLATE utf8mb4_unicode_ci,
  `driver_notes` text COLLATE utf8mb4_unicode_ci,
  `declined_at` timestamp NULL DEFAULT NULL,
  `accepted_at` timestamp NULL DEFAULT NULL,
  `cancelled_at` timestamp NULL DEFAULT NULL,
  `delivery_at` timestamp NULL DEFAULT NULL,
  `completed_at` timestamp NULL DEFAULT NULL,
  `delivery_type` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `tips` decimal(11,2) DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `order_store_id_foreign` (`store_id`),
  KEY `order_user_id_foreign` (`user_id`),
  CONSTRAINT `order_store_id_foreign` FOREIGN KEY (`store_id`) REFERENCES `store` (`id`),
  CONSTRAINT `order_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10268 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

路线

Route::match(array('GET', 'POST'), '/all_orders','OrderController@all_orders')->name('all_orders');

enter image description here

我尝试了下面的代码,并且比以前 59.0 秒到 5.0 秒更快地获取数据,现在这是一个很好的结果,但我不知道代码是否是一种有效的方法,请指导我

$orders = Order::latest();
$search = $request->search['value'];
$filter_type = $request->filter_type;
if ($filter_type == "custom") {
    $from = date('Y-m-d' . ' 00:00:00', strtotime($request->from_dates));
    if ($request->has('to_dates')) {
        $to = date('Y-m-d' . ' 23:59:59', strtotime($request->to_dates));
        $orders = $orders->whereBetween('created_at', array($from, $to));
    }
}
elseif ($filter_type == "daily") {
    $orders = $orders->where('created_at', '>=', Carbon\Carbon::today());
}
elseif ($filter_type == "weekly") {
    $fromDate = Carbon\Carbon::now()->subDay()->startOfWeek()->toDateString();
    $tillDate = Carbon\Carbon::now()->subDay()->endOfWeek()->toDateString();
    $orders = $orders->whereBetween(DB::raw('date(created_at)'), [$fromDate, $tillDate]);
}
elseif ($filter_type == "monthly") {
    $orders = $orders->whereRaw('MONTH(created_at) = ?', [date('m')]);
}
elseif ($filter_type == "yearly") {
    $orders = $orders->whereRaw('YEAR(created_at) = ?', [date('Y')]);
}
return Datatables::of($orders)
    ->addColumn('id', function ($orders) {
        return @$orders->id;
    })
    ->addColumn('payment_type', function ($orders) {
        return @$orders->payment_type_text;
    })
    ->addColumn('user_name', function ($orders) {
        return @$orders->user->name;
    })
    ->addColumn('user_address', function ($orders) {
        return @$orders->user->user_address->first_address;
    })
    ->addColumn('mobile_number', function ($orders) {
        return @$orders->user->mobile_number;
    })
    ->addColumn('store_name', function ($orders) {
        return @$orders->store->name;
    })
    ->addColumn('service_type', function ($orders) {
        return @$orders->store->service_type1->service_name;
    })
    ->addColumn('total', function ($orders) {
        return html_entity_decode(currency_symbol() . (@$orders->total_amount+@$orders->wallet_amount));
    })
    ->addColumn('status_text', function ($orders) {
        return @$orders->status_text;
    })
    ->addColumn('created_at', function ($orders) {
        return @date("d-m-y h:i A", strtotime($orders->created_at));
    })
    ->addColumn('action', function ($orders) {
        return '<a title="' . trans('admin_messages.view') . '" href="' . route('admin.view_order', $orders->id) . '" ><i class="material-icons">edit</i></a>';
    })->rawColumns(['id', 'payment_type','user_name','store_name','user_address','mobile_number','service_type','total','status_text','action'])
    ->make(true);

MySQL Laravel DataTable 服务器端

评论

0赞 Rick James 9/25/2021
请提供生成的 SQL;这样我就可以更容易地找到性能问题的根源。另外,请提供所涉及的任何表格。该问题可能涉及引擎和/或索引问题。SHOW CREATE TABLE
0赞 user3837868 9/26/2021
@RickJames 请检查更新的问题
0赞 Rick James 9/26/2021
是表吗?生成的在哪里?InnoDBSELECT
0赞 Rick James 9/26/2021
另外,有多少RAM和值是多少?innodb_buffer_pool_size
0赞 user3837868 9/26/2021
innodb_buffer_pool_size = 134217728,服务器内存为 6GB

答:

1赞 Rick James 9/26/2021 #1

“表格扫描”正在扼杀性能。

  • 更大的缓存可能会有所帮助
  • 更好的指数可能会有所帮助

查看使用 MySQL 的服务器上未使用的 RAM 量。增加以包括大部分 RAM。innodb_buffer_pool_size

(128M 是一个旧的默认值;对于当今大多数硬件上的大多数应用程序来说,这个数字低得可怜。

如果您运行的是旧版本的 MySQL,请考虑升级(出于多种原因)。

MONTH(created_at) = ?效率远低于

WHERE created_at >= '2021-02-01'
  AND created_at  < '2021-02-01' + INTERVAL 1 MONTH

但是,这种性能提升假设某些索引包括 。现在不存在。created_at

如果我能看到生成的典型 SQL,我可以进一步建议更好的索引。SELECT

评论

0赞 user3837868 9/26/2021
我可以增加多少innodb_buffer_pool_size?
0赞 Rick James 9/26/2021
如果该服务器仅运行 MySQL,请设置为大约 70% 的 RAM (4G)。如果您的应用也在运行,请为其留出空间。
0赞 user3837868 9/26/2021
是的,我就这样改变了,但结果是一样的
0赞 Rick James 9/26/2021
@user3837868 - 显示生成的 SQL 查询。我的拉维尔语不流利。
0赞 user3837868 9/26/2021
我不认为查询会影响结果,因为我使用了没有任何查询的 SELECT ALL,但结果是相同的
1赞 Ben Gooding 9/27/2021 #2

您正在为找到的每个订单触发多个查询。通过使用 and it'll only fire's 1 query per table:with

$orders = $orders->with(['user', 'store.service_type1'])->get();

https://laravel.com/docs/8.x/eloquent-relationships#eager-loading

如果您找到 500 个订单,您将触发 1000 多个查询。这会将其减少到 4!

(如果你愿意的话,可以额外增加一点:)如果你使用关系而不是 (在引擎盖下使用 a 而不是 2 个查询执行 1 个查询),你可以把它变成 3 个throughstore.service_type1join

评论

0赞 user3837868 9/28/2021
嗨,我尝试了您的解决方案,但结果仍然相同
0赞 user3837868 9/29/2021
更新的问题
1赞 Mtxz 9/28/2021 #3

主要问题是您的最终查询:。您没有提供任何分页/限制参数,因此此查询将查询整个订单表,并在集合中给出结果。如果有 10.000 行与筛选器匹配,则这些行将在单个集合中返回。$orders->get();

检查文档,但我认为 Datatables 接受 Eloquent 实例,而不仅仅是 Collections。

因此,首先,尝试删除该行以直接将 Eloquent 查询实例传递给 Datatable。$orders = $orders->get();

其次,再次检查文档,但我认为 Datatable 有一种内置的方式来处理分页(重新加载数据并从前端发送正确的 HTTP 参数(下一个/上一个或分页单击、排序、搜索......),将分页应用于 Eloquent 查询)。如果是这样:尝试使用它(如果它不能直接开箱即用,则在将 Eloquent 实例而不是 Collection 传递给 Datatable laravel 方法时)。如果没有,则需要在 HTTP 请求中实现分页参数,并将其应用于雄辩的查询。

小心,因为数据表将工作(并且很慢,因为 HTTP 响应很大,并且 DOM 也变得很大),在一个请求中返回 10.000 行集合。排序会起作用,搜索也会起作用,但很差,因为 JS 只有排序(CPU 贪婪,慢)。这显然不是你应该做的。

最后,所有前端操作:文本搜索、列排序、分页......应该使用自定义 GET 参数(用于搜索、排序、分页等)请求您的 Laravel,并且您的 Laravel 控制器应该将这些 GET 参数“翻译”为 Eloquent 方法/查询。还要注意@BenGooding提到的急切加载。

OP 问题更新后的更新:OP 编辑了它的问题和代码片段。在问题的第一个版本中,OP 执行 a 没有任何分页或限制。它查询了整个表,Databable 方法被赋予了一个巨大的集合,其中包含所有对象。然后,前端 JS 数据表将再次处理数百行,从而进一步减慢页面速度。->get()

根据我的建议,OP 更改了他的代码:与其查询整个表,不如将 Eloquent 查询传递给 Datatable 帮助程序,因为这个库可以处理查询分页和一些过滤本身,如果你给它一些东西来工作(不是明确的对象列表,而是一个要适应的 SQL 查询)。在此之后,OP 将查询时间从 50 秒缩短到 5 秒。因此,这是他的数据表管理的主要问题。

评论

0赞 user3837868 9/28/2021
嗨,我尝试了您的解决方案,但遇到一些错误可以提供代码示例和数据表处理分页
0赞 Mtxz 9/28/2021
请发布您的错误。您的代码已经接近您的需求
0赞 user3837868 9/28/2021
“传递给 App\\Http\\Controllers\\DataTableBase::__construct() 的参数 2 必须是 Yajra\\DataTables\\CollectionDataTable 的实例,给定的 Yajra\\DataTables\\EloquentDataTable 的实例,在第 544 行的 /app/Http/Controllers/Admin/OrderController.php 中调用”
0赞 user3837868 9/28/2021
这个错误,因为这个权利我通过这里$base = new DataTableBase($orders, $datatable, $columns, 'Orders');$orders
0赞 Mtxz 9/28/2021
请参阅文档示例: datatables.yajrabox.com - 您可以尝试删除对象并将对象返回到前端吗?如果它不起作用,您可以尝试像示例文档中那样链接。但是正如您在文档中看到的那样,Datatable 接受 Eloquent 实例,但我没有看到像您那样的 DataTable 基础实现。它直接返回 Datatable 实例。$base = new DataTableBase($orders, $datatable, $columns, 'Orders');$datatable->make(true);
1赞 Muzaffar Shaikh 9/28/2021 #4

启用查询日志并检查查询花费了多少时间,这将使您了解表是否真的需要更新。 我在下面提到一些无论如何都可以在桌面上完成的要点。

  1. 我看到有一个列driver_id但您没有在其上添加任何索引或约束。
  2. 如果此列不是外键,则添加基本索引,对promo_id列执行相同的操作。
  3. 在列delivery_type添加索引。
  4. 如果可以,请在 payment_type 上创建位图索引,然后payout_status
  5. 最后,我看到您正在对时间戳列运行查询,如果可能的话,也要为此添加索引。

所有这些都将有助于减少从数据库获取数据的时间。

在代码端,如果要从不同的表和关系中获取数据,请考虑使用 with([])。 这将通过减少运行的查询数来提供帮助。

如果所有这些都没有帮助,请检查代码处理请求所需的时间。 有了所有这些,至少你应该能够知道表格是问题还是代码。

评论

0赞 user3837868 9/29/2021
更新的问题
1赞 swap 10/1/2021 #5
  1. 获取您想要的特定选择列?
  2. datatables()->eloquent($query) DataTables::of($orders) 实例(发送到未传递查询结果的对象。
  3. 创建单独的数据表处理文件,如数据表服务文件。
public function dataTable($query)
{
    return datatables()
        ->eloquent($query)
        ->editColumn('status', function ($query) {
            ...
        })
        ->addColumn('action', function ($query) {
            ...
        })
        ->addIndexColumn()
        ->rawColumns(['status', 'action']);
}

public function query(Category $model)
{
    return $model->newQuery();
}
1赞 Foramkumar Patel 10/1/2021 #6

您有 2 个选项可以解决问题

  1. 将缓存添加到前端和后端,这样就不会花费时间。您也可以使用mysql查询缓存
  2. 在 laravel 中使用 eloquent orm,所以需要时间,所以尝试使用简单的 mysql 查询,这样执行时间就更少了
1赞 Marwane Ezzaze 10/2/2021 #7

你应该做的是对结果进行分页,因为我在你的PHP端没有看到任何分页,我想你有数千条记录,这需要很长时间才能得到。 datatable 当 serverSide 被激活时,它会向后端发送一堆关于查询的 GET 参数,其中包括 startlength,所以在你的控制器上,你应该利用这些参数

1赞 svin83 10/2/2021 #8

代码的可读性和性能

  • 当您使用循环和条件分支时,优化代码非常重要。
  • 我会用代替所有这些条件。switchelseif

执行锁

  • 在 PHP 代码执行和 HTML 输出之间来回切换总是会减慢速度。有时很多。
  • 在字符串变量中生成 HTML,而不是打印到缓冲区/输出,然后在完成后一次输出整个页面。