提问人:user3837868 提问时间:9/20/2021 最后编辑:user3837868 更新时间:2/9/2022 访问量:6518
Laravel 数据表加载数据需要花费太多时间
Laravel datatable taking too much of time to load data
问:
这里使用下面的代码加载订单数据,但加载时间太长,我也在使用 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');
我尝试了下面的代码,并且比以前 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 的服务器上未使用的 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
评论
您正在为找到的每个订单触发多个查询。通过使用 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 个through
store.service_type1
join
评论
主要问题是您的最终查询:。您没有提供任何分页/限制参数,因此此查询将查询整个订单表,并在集合中给出结果。如果有 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 秒。因此,这是他的数据表管理的主要问题。
评论
$base = new DataTableBase($orders, $datatable, $columns, 'Orders');
$orders
$base = new DataTableBase($orders, $datatable, $columns, 'Orders');
$datatable
->make(true);
启用查询日志并检查查询花费了多少时间,这将使您了解表是否真的需要更新。 我在下面提到一些无论如何都可以在桌面上完成的要点。
- 我看到有一个列driver_id但您没有在其上添加任何索引或约束。
- 如果此列不是外键,则添加基本索引,对promo_id列执行相同的操作。
- 在列delivery_type添加索引。
- 如果可以,请在 payment_type 上创建位图索引,然后payout_status
- 最后,我看到您正在对时间戳列运行查询,如果可能的话,也要为此添加索引。
所有这些都将有助于减少从数据库获取数据的时间。
在代码端,如果要从不同的表和关系中获取数据,请考虑使用 with([])。 这将通过减少运行的查询数来提供帮助。
如果所有这些都没有帮助,请检查代码处理请求所需的时间。 有了所有这些,至少你应该能够知道表格是问题还是代码。
评论
- 获取您想要的特定选择列?
- datatables()->eloquent($query) DataTables::of($orders) 实例(发送到未传递查询结果的对象。
- 创建单独的数据表处理文件,如数据表服务文件。
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();
}
您有 2 个选项可以解决问题
- 将缓存添加到前端和后端,这样就不会花费时间。您也可以使用mysql查询缓存
- 在 laravel 中使用 eloquent orm,所以需要时间,所以尝试使用简单的 mysql 查询,这样执行时间就更少了
你应该做的是对结果进行分页,因为我在你的PHP端没有看到任何分页,我想你有数千条记录,这需要很长时间才能得到。 datatable 当 serverSide 被激活时,它会向后端发送一堆关于查询的 GET 参数,其中包括 start 和 length,所以在你的控制器上,你应该利用这些参数
代码的可读性和性能
- 当您使用循环和条件分支时,优化代码非常重要。
- 我会用代替所有这些条件。
switch
elseif
执行锁
- 在 PHP 代码执行和 HTML 输出之间来回切换总是会减慢速度。有时很多。
- 在字符串变量中生成 HTML,而不是打印到缓冲区/输出,然后在完成后一次输出整个页面。
评论
SHOW CREATE TABLE
InnoDB
SELECT
innodb_buffer_pool_size