Laravel Livewire 大批量交易数据分析计算加载缓慢优化咨询
性能优化方案
1. 数据库查询层优化(收益最高,优先改)
- 把「先查全量交易再用集合过滤时间」的逻辑改成「先加时间过滤条件再查询」,直接减少返回的数据集大小,选7D/1M这类短周期时数据量会下降90%以上。
- 给
trades表添加联合索引idx_portfolio_entry_status(portfolio_id, entry_date, status),所有查询都会走索引,查询速度提升10倍以上。 - 简单聚合运算直接用SQL完成,不要拉到PHP层计算,比如总和、计数、最大最小值可以直接用
sum()、count()、max()、min()SQL函数处理。
filterTrades修改示例:
public function filterTrades() { $query = $this->portfolio->trades(['return', 'return_percentage', 'status', 'entry_date', 'id']) ->orderBy('entry_date', 'desc'); // 时间条件先加在查询构造器上,再执行查询 switch ($this->filter) { case '7D': $query->whereBetween('entry_date', [now()->startOfWeek(), now()->endOfWeek()]); break; case '1M': $query->whereBetween('entry_date', [now()->startOfMonth(), now()->endOfMonth()]); break; case '3M': $query->whereBetween('entry_date', [now()->startOfMonth()->subMonths(2), now()->endOfMonth()]); break; case '6M': $query->whereBetween('entry_date', [now()->startOfMonth()->subMonths(5), now()->endOfMonth()]); break; case '1Y': $query->whereBetween('entry_date', [now()->startOfYear(), now()->endOfYear()]); break; case '2Y': $query->whereBetween('entry_date', [now()->startOfYear()->subYears(1), now()->endOfYear()]); break; case '3Y': $query->whereBetween('entry_date', [now()->startOfYear()->subYears(2), now()->endOfYear()]); break; } return $query->get(); }
2. 计算逻辑优化(减少重复遍历,效率提升数倍)
你当前的Service里存在大量重复的集合遍历:每算一个指标就要遍历一次所有交易,1万条数据的场景下等于要遍历十几次,改成单次遍历计算所有基础指标,后续直接返回预计算的结果即可。
修改后的TradeAnalyticsService示例:
class TradeAnalyticsService { private $trades; private $balance = 0; // 预计算基础指标 private $winSum = 0; private $loseSum = 0; private $winCount = 0; private $loseCount = 0; private $bestTrade; private $worstTrade; private $longestWinStreak = 0; private $longestLoseStreak = 0; public function __construct($trades, int $balance) { $this->trades = $trades; $this->balance = $balance; $this->calculateBaseMetrics(); // 构造时仅遍历一次算完所有基础指标 } // 单次遍历计算所有基础指标 private function calculateBaseMetrics() { $currentWinStreak = 0; $currentLoseStreak = 0; $maxReturn = -PHP_INT_MAX; $minReturn = PHP_INT_MAX; foreach ($this->trades as $trade) { if ($trade->status === 'win') { $this->winSum += $trade->return; $this->winCount++; // 连胜计算 $currentWinStreak++; $currentLoseStreak = 0; $this->longestWinStreak = max($this->longestWinStreak, $currentWinStreak); // 最高收益交易计算 if ($trade->return > $maxReturn) { $maxReturn = $trade->return; $this->bestTrade = $trade; } } elseif ($trade->status === 'lose') { $this->loseSum += $trade->return; $this->loseCount++; // 连败计算 $currentLoseStreak++; $currentWinStreak = 0; $this->longestLoseStreak = max($this->longestLoseStreak, $currentLoseStreak); // 最低收益交易计算 if ($trade->return < $minReturn) { $minReturn = $trade->return; $this->worstTrade = $trade; } } } } // 后续所有get方法直接返回预计算结果,无需再次遍历 public function getProfitFactor() { $loseAbs = abs($this->loseSum); return ($this->winSum < 1 || $loseAbs < 1) ? 0 : $this->winSum / $loseAbs; } public function getPercentProfitable() { $total = $this->winCount + $this->loseCount; return ($this->winCount < 1 || $total < 1) ? 0 : $this->winCount / $total * 100; } public function getAverageTradeNetProfit() { $total = $this->winCount + $this->loseCount; return $total < 1 ? 0 : ($this->winSum + $this->loseSum) / $total; } public function getAverageWinner() { return $this->winCount < 1 ? 0 : $this->winSum / $this->winCount; } public function getAverageLoser() { return $this->loseCount < 1 ? 0 : $this->loseSum / $this->loseCount; } public function getBestTradeReturn() { return $this->bestTrade; } public function getWorstTradeReturn() { return $this->worstTrade; } public function getWinCount() { return $this->winCount; } public function getLoseCount() { return $this->loseCount; } public function getTradeCount() { return $this->trades->count(); } public function getNetProfit() { return $this->winSum + $this->loseSum; } public function getLongestWinStreaks() { return $this->longestWinStreak; } public function getLongestLoseStreaks() { return $this->longestLoseStreak; } // 其他方法保持原有逻辑即可 }
3. 缓存优化(降低重复计算开销)
- 给每个组合+筛选维度的计算结果加缓存,缓存Key规则可以设为
analytics:portfolio:{组合ID}:filter:{筛选维度},缓存有效期设为5分钟即可。 - 当组合下有新增/修改/删除交易时,主动清空该组合对应的所有分析缓存,保证数据准确性。
- 如果仪表盘实时性要求不高,可以提前预计算天、周、月维度的统计数据,存在单独的统计表中,查询时直接读预计算结果,速度可以达到毫秒级。
4. 额外优化点
- 取消不必要的字段查询:如果后续逻辑不需要
instrument字段,就不要在查询时select该字段,减少数据传输开销。 - 收益趋势的分组求和可以直接在SQL层面完成,用
DATE_FORMAT(entry_date, "%d/%m/%y")分组并sum(return),无需拉到PHP层做分组计算。
内容的提问来源于stack exchange,提问作者Sherwin Variancia
相关产品推荐
相关产品推荐

