在我正在开发的应用程序中,我们有两个数据源:MySQL 和 Elasticsearch。粗略地说,ES 有一份 MySQL 数据的副本,以获得更好的性能。我建立了一个直接访问MySQL的管理面板。这对于写入操作和检索单个项目来说是可以的。但是,当我尝试列出和过滤大量记录时,速度非常慢,导致超时。然后,我只更新列表部分以从 ES 而不是 MySQL 获取数据。
该面板基于 Laravel 5.3 构建,我们使用 Datatables jQuery 插件 来显示数据。要将 Datatables 绑定到后端,我们有 Laravel 的 Datatables 包。
通过以下控制器操作,并且在不应用任何过滤器时,它可以正常工作:
public function data(Elasticsearch $elastic, Request $request)
{
$query = [];
$cols = [];
if ($request->has('columns')) {
foreach ($request->get('columns') as $column) {
$cols[] = $column['name'];
if (!empty($column['search']['value'])) {
$query[] = sprintf('%s:%s', $column['name'], $column['search']['value']);
}
}
}
$from = (int) $request->get('start');
$size = (int) $request->get('length');
$sort = [];
if ($request->has('order')) {
foreach ($request->get('order') as $order) {
$sort[] = [ $cols[$order['column']] => $order['dir'] ];
}
} else {
$sort[] = [ 'name' => 'asc' ];
}
$args = [
'index' => 'acme',
'type' => 'user',
'from' => $from ?: 0,
'size' => $size ?: 10,
'sort' => $sort,
];
if (count($query) > 0) {
$args['q'] = implode(' AND ', $query);
}
$response = $elastic->search($args);
$data = [];
$total = 0;
if (!empty($response['hits'])) {
$total = $response['hits']['total'];
$data = array_map(function ($hit) {
return $hit['_source'];
}, $response['hits']['hits']);
}
return Datatables::of(collect($data))
->skipPaging()
->setTotalRecords($total)
->make(true);
}
触发表格渲染的Javascript如下所示:
$('#users-table').DataTable({
processing: true,
serverSide: true,
orderCellsTop: true,
ajax: '{!! route('user.data') !!}',
columns: [
{ data: 'name', name: 'name' },
{ data: 'type', name: 'type' },
{ data: 'status', name: 'status' }
]
});
当我尝试按某些列搜索或过滤表时,会出现问题。分页停止工作。我想这是因为我在将数据集传递到数据表之前对其进行了过滤,在
Datatables::of()
中。我知道我可以从 ES 检索整个数据集并让 Datatables 进行数据过滤和排序,但我可能最终会遇到时间和内存使用问题。我们索引了数百万份文档。
我尝试使用空函数(
->filter(function () {})
)覆盖全局搜索,但它不起作用。当我添加此内容时,分页中断,即使记录总数设置为很大的值,也仅显示一页。
通过查看 Laravel DataTables 源代码,有未记录的
overrideGlobalSearch()
方法,请参阅下面的定义。最后一个参数是一个标志,用于确定是否应执行默认全局搜索。
/**
* Update flags to disable global search
*
* @param callable $callback
* @param mixed $parameters
* @param bool $autoFilter
*/
public function overrideGlobalSearch(callable $callback, $parameters, $autoFilter = false)
{
}
理论上您应该能够执行以下操作:
$datatables = Datatables::of(collect($data))
->skipPaging()
->setTotalRecords($total);
$datatables->overrideGlobalSearch(function(){ }, null);
return $datatables->make(true);
您还需要重置保存列搜索值的参数,以便 DataTables 默认情况下不执行列搜索。
除此之外,请确保您的
$total
变量保存过滤之前的记录总数。
当我评论 Gyrocode 的答案时,我找到了一种让它按照我想要的方式工作的方法。我想还有更优雅的方法可以做到这一点,但这个有效。
由于我正在使用的 Datatables 库采用当前请求来对数据进行排序和过滤,因此我研究了源代码以找到使用空请求的方法。我最终发现可以实例化引擎来传递我想要的请求。
这就是我所做的:
use Yajra\Datatables\Engines\CollectionEngine;
use Yajra\Datatables\Request as DatatablesRequest;
// ...
return value(new CollectionEngine(collect($data), new DatatablesRequest()))
->setTotalRecords($total)
->make(true);
这样,库将按原样使用数据,并且不进行任何类型的过滤或排序。