【问题标题】:Laravel Paginate method takes too much time for 1 million recordLaravel Paginate 方法对于 100 万条记录需要太多时间
【发布时间】:2022-10-21 23:00:53
【问题描述】:

所以这是我的情况。我有一个主表 payment_transactions 有近 100 万条记录。 我正在使用连接和 where 子句从该表中获取数据,并且有 Laravel paginate 方法, 但是这种方法耗时太长,经过调查,我发现它的计数方法仅用于计数就需要4到5秒的时间。

那么如何优化和加速这个查询,特别是有什么方法可以提高分页方法的速度呢?

Note: 我不能使用 simplePaginate 因为前端有一个数据表,我需要一个总数。

所以对于分页,两个查询运行 1是主查询,另一个是count,我觉得count查询很费时间。

这是getQueryLog之后的计数查询

select count(*) as aggregate from `payment_transactions` 
left join `users` as `U` on `U`.`id` = `payment_transactions`.`user_id`
left join `coupons` as `C`
on `C`.`id` = `payment_transactions`.`coupon_id` 
where `payment_transactions`.`refund_status` = 'NO_REFUND'
and `payment_transactions`.`transaction_type` 
in ('BOOKING','SB_ANPR','QUERCUS_ANPR','CANDID_ANPR','SB_TICKET',
'ORBILITY_TICKET','TOPUP,CREDIT','DEBIT','GIFT')
and `payment_transactions`.`status` != 'INITIATED'

这是我的代码示例:

//Get Transactions data
public function adminTransactions(Request $request) 
{
    $selectableFields = [
            'payment_transactions.id', 'payment_transactions.transaction_id AS transaction_id',
    'payment_transactions.refund_status',
            'payment_transactions.created_at', 'payment_transactions.response_data', 'payment_transactions.status',
            'payment_transactions.transaction_type', 'payment_transactions.payment_mode','payment_transactions.payment_source',
            'payment_transactions.subtotal', 'payment_transactions.service_fees', 'C.coupon_code','C.amount AS coupon_value',
            DB::raw("IF(payment_transactions.refund_remarks='NULL','-NA-',payment_transactions.refund_remarks) as refund_remarks"),
            DB::raw("IF(payment_transactions.transaction_type='TOPUP' AND payment_transactions.coupon_id IS NOT NULL 
    AND payment_transactions.coupon_id!=0,
payment_transactions.amount + C.amount,payment_transactions.amount) as amount"),
            DB::raw("CONCAT(U.first_name,' ',U.last_name)  AS username"), 'U.id AS user_id',
            DB::raw("JSON_UNQUOTE(json_extract(payment_transactions.response_data, '$.description')) AS description"),
            DB::raw("payment_transactions.invoice_id"),
            DB::raw("JSON_UNQUOTE(json_extract(payment_transactions.response_data, '$.Data.PaymentID')) AS upay_payment_id"),
        ];
       
    return PaymentTransactions::select($selectableFields)
            ->with('homeScreenMessages:payment_transaction_id,from_name,message,amount')
            ->leftJoin('users AS U', 'U.id', '=', 'payment_transactions.user_id')
            ->leftJoin('coupons AS C', 'C.id', '=', 'payment_transactions.coupon_id')
        ->where(DB::raw("CONCAT(U.first_name,' ',U.last_name)"), 'like', "%{$request->input('query')}%")
            ->orWhere('U.id', $request->input('query'))
            ->orWhere("U.phone_number", "LIKE", "%" . $request->input('query') . "%")
            ->orWhere("U.email", "LIKE", "%" . $request->input('query') . "%")
        ->orWhere('payment_transactions.id', $request->input('query'))
            ->orWhere('payment_transactions.transaction_id', $request->input('query'));
}

//Paginate function
 public function paginationCalculate($queryObject, $request) {
        $draw = $request->get('draw');
        $start = $request->get("start");
        $rowperpage = $request->get("length"); // Rows display per page

        $columnIndex_arr = $request->get('order');
        $columnName_arr = $request->get('columns');
        $order_arr = $request->get('order');

        $columnIndex = $columnIndex_arr[0]['column']; // Column index
        $columnName = $columnName_arr[$columnIndex]['name']; // Column name
        $columnSortOrder = $order_arr[0]['dir']; // asc or desc
        $pageNumber = ($start + $rowperpage) / $rowperpage;
        if(!empty($columnName)) {
            $queryObject->orderBy($columnName, $columnSortOrder);
        }
        $records = $queryObject->paginate($rowperpage, ['*'], 'page', $pageNumber)->toArray();

        return array(
            "draw" => intval($draw),
            "recordsFiltered" => $records['total'],
            "recordsTotal" => $records['total'],
            "data" => $records['data']
        );
    }

【问题讨论】:

  • 我假设您已经索引了正确的列?如果删除所有包含$request->input('query') 的行,您可以测试查询需要多长时间。如果速度足够快,那么您就知道这是导致速度缓慢的原因。还有其他方法可以在数据库中搜索,但在您查看之前,您首先需要确认这确实是原因。
  • @KIKOSoftware,我在数据库中添加了所有可能的index,并在没有$request->input 的情况下进行了测试,在getQueryLog 之后我发现分页计数需要时间,所以我删除了它只是为了测试并注意几乎减少了 4 到 5 秒的时间。
  • 抱歉,我阅读了您的回答,但它仍然不知道删除所有包含 $request->input('query') 的行有什么影响。请注意,我写的是“删除所有行”,而不仅仅是空的$request->input('query')。如果您使用过getQueryLog,并且您认为您知道问题所在,请告诉我们,最好是通过编辑您的问题,而不是在评论中。你在说什么样的“分页计数”?
  • 感谢您编辑您的问题。好的我明白了。我注意到计数查询连接了userscoupons 表,但它们实际上并未用于创建计数。如果删除它们,然后确保 transaction_typestatus 列有索引,那么该查询应该很快。请注意,您可以将多个列组合成一个索引。
  • @KIKOSoftware,感谢您的回复,我会按照您的建议尝试。但是我在添加索引后尝试过,但我认为没有像你说的那样尝试使用复合索引。

标签: php mysql laravel optimization pagination


【解决方案1】:

我不认为你想要“左”。使用 LEFT,即使没有匹配的用户和/或优惠券,COUNT 也会计算付款交易。

或者也许有每个 payment_transaction 的用户或优惠券?在这种情况下,COUNT 将被夸大。要解决此问题,只需删除两个额外的表。

不管情况如何,将此复合(和覆盖)索引添加到payment_transactions

INDEX(refund_status, transaction_type, status, user_id, coupon_id)

refund_type 必须是第一个,因为它是用 '=' 测试的。

如果这还不够“快”,那么我必须问你“当有一百万行时,谁在乎确切的数字是多少?”你有没有看到一个搜索引擎给出了准确的点击数?这种不精确会困扰你吗?

也就是说,重新考虑计算确切行数的要求。甚至是一个大概的数字。

您提到了“分页”。请告诉我们这是如何做到的。 OFFSET 是一种低效的方法,因为它一次又一次地重新扫描先前看到的行。相反,“记住你离开的地方”:Pagination

【讨论】:

    【解决方案2】:

    您必须将 INDEX 添加到您的 MySQL 数据库中。让它快速点亮!

    如果您使用 Navicat,请单击索引选项卡,然后添加列名称并将其命名为相同。你会注意到一个巨大的差异。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-01
      相关资源
      最近更新 更多