关于这个问题的一些背景知识:
我工作的公司提供与Booking.com/Airbnb等类似的服务。房产所有者在网站上列出他们的房产,他们得到预订作为回报。
在月底,我必须生成发票并发送到这些属性。为了生成这些发票,我使用远在2018年4月1日的记录。对于特定的房产,我搜索其相关的预订,然后迭代结果集来计算我们的佣金。
随着公司的发展,物业和预订的数量也在增加。现在有超过100个物业,其中一些物业有超过2000个预订。要生成一张发票,需要花费很长时间,大多数时间超过一分钟。
我想知道处理大型结果集这样的最佳实践,因为我觉得当前的实现效率很低。
我唯一能想到的就是计算每个房产的所有应付金额并将其写入单独的表格,然后在每次预订时修改该金额。然后我可以使用该表中的金额来获得一个月底欠的会费。但我不认为这是正确的,因为它通常是最好的做法,不保持数据库中的派生属性。
我使用PHP服务器端,Laravel 5.4和MySQL 5.7是DBMS。
任何帮助,将不胜感激。谢谢!
PS:我也尝试过使用PHP Collections上提供的chunk
方法,但它们没有显着减少时间。
编辑:我错过了一个非常重要的部分。调试时我可以清楚地看到,花费在计算/处理数千条记录上的时间最多。不是查询本身。即使网络状况不佳,记录也会在不到10秒的时间内获取,大多数时间不到5秒。我会在我的工作机器上显示代码但我现在无法访问它。这是我在家里考虑的事情。基本上代码是这样的:
$properties = Property::where('status', 'Active');
foreach($properties as $property) { // 100+ times
$bookings = Booking::where('status', 'Approved')->where('checked_in', 1)->where('propId', $property->id)->get();
$commission = 0.0; // commissions owed to us
$commissionOta = 0.0; // any commissions owed to OTA's
foreach($bookings as $booking) { // 2000+ times
$commission += $booking->commission;
$commissionOta += $booking->commissionOta;
}
$totalReceived = PaymentReceived::where('propId', $property->id)->sum('amount'); // any amounts paid by the property owner
$property->payable = ($commission + $commissionOta) - $totalReceived;
}
目前尚不清楚你在寻求什么。但总的来说,我强烈建议避免在循环中查询:
所以你的代码片段:
$properties = Property::where('status', 'Active');
foreach($properties as $property) { // 100+ times
$bookings = Booking::where('status', 'Approved')->where('checked_in', 1)->where('propId', $property->id)->get();
$commission = 0.0; // commissions owed to us
$commissionOta = 0.0; // any commissions owed to OTA's
foreach($bookings as $booking) { // 2000+ times
$commission += $booking->commission;
$commissionOta += $booking->commissionOta;
}
$totalReceived = PaymentReceived::where('propId', $property->id)->sum('amount'); // any amounts paid by the property owner
$property->payable = ($commission + $commissionOta) - $totalReceived;
}
似乎只等于这个查询:
SELECT p.id,
SUM(b.commission) s_comm,
SUM(b.commissionOta) s_commOta,
SUM(b.commission) + SUM(b.commissionOta) - pr.amount payable
FROM Property p
INNER JOIN Booking b
ON b.propOd = p.id
AND b.status = 'Approved'
AND checked_in = 1
LEFT JOIN (
SELECT pr.propId, SUM(pr.amount) amount
FROM PaymentReceived pr
GROUP BY pr.propId
) pr
ON pr.propId = p.id
WHERE p.status = 'Active'
GROUP BY p.id
尝试运行此查询。检查结果集和性能,并尝试调整代码以使用一个查询而不是循环数据库请求。
对于Laravel,它应该是这样的:
$result = DB::select('SELECT p.id,
SUM(b.commission) s_comm,
SUM(b.commissionOta) s_commOta,
SUM(b.commission) + SUM(b.commissionOta) - pr.amount payable
FROM Property p
INNER JOIN Booking b
ON b.propOd = p.id
AND b.status = \'Approved\'
AND checked_in = 1
LEFT JOIN (
SELECT pr.propId, SUM(pr.amount) amount
FROM PaymentReceived pr
GROUP BY pr.propId
) pr
ON pr.propId = p.id
WHERE p.status = \'Active\'
GROUP BY p.id');
foreach($result as $r){
echo 'Property id: '.$r->id.' Commission: '.$r->s_comm. ' Commission Ota: '.$r->commissionOta .' Payable: '.$r->payable ;
}
您应该尝试实施的另一个想法是削减/关闭期间。现在,如果我理解正确,您将从每个月或其他时间段开始重新计算所有数据。但通常您应该从上一期间存储一些平衡结果,并仅重新计算上一期间数据的差异。
好吧,我有一些“大数据”的经验(好吧,不是那么大但不小 - 我正在开发电信,我有一个通常超过20万条记录的通话详情表)。我可以告诉你,通过适当的索引,搜索速度非常快。我刚刚在非索引列上的116803行的表中进行了测试,得到了结果:
Showing rows 0 - 24 (1608 total, Query took 0.0016 seconds)
现在,在同一个表上,在索引列上:
Showing rows 0 - 0 (1 total, Query took 0.0008 seconds.)
此外,如果您使用连接(左,右,内等),请在索引列上使用它们。这样,对于每一行,sql不必搜索整个连接表。
所以,如果您有下一个查询:
SELECT * FROM table_a INNER JOIN table_b ON column_from_a = column_from_b
如果table_b非常大,请确保将column_from_b
编入索引。
另一件事(但有点复杂)是复制。你可以有一个sql集群,比如3个服务器。您可以将主应用程序设置为使用其中的2个,并将第3个保留用于某些密集操作。
但最好的解决方案是了解如何为特定类型的操作提供自定义解决方案。没有可以在任何地方实施的通用配方。