如何高效处理亚马逊报表文件?Doctrine关联优化求助
我处理过不少亚马逊大报表的导入场景,给你几个实用的优化方向,能大幅降低脚本运行时间,同时让代码更专业:
你当前循环执行单条UPDATE来关联Stock和StorageFee的方式,是性能瓶颈的核心——35000行数据就要执行35000次数据库查询,耗时肯定拉满。
更合理的做法是直接用ASIN作为实体间的关联字段,不需要额外的storageFeeId。修改实体关联配置:
// Stock.php /** * @OneToOne(targetEntity="StorageFee") * @JoinColumn(name="asin", referencedColumnName="asin") */ private $storageFee; // StorageFee.php // 先给ASIN字段加唯一约束,确保每个ASIN只有一条记录 /** @ORM\Entity(repositoryClass="App\Repository\StorageFeeRepository") */ @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(name="asin_unique", columns={"asin"})}) class StorageFee { // ... 其他字段 /** @ORM\Column(type="string", length=16, unique=true) */ private $asin; }
这样只要StorageFee数据先导入(或和Stock同时导入),Doctrine会自动通过ASIN完成关联,完全不需要后续的批量更新步骤。
Doctrine的persist+flush虽然方便,但在处理上万条数据时,ORM的对象跟踪机制会带来额外的内存和性能开销。直接用原生SQL批量插入,能把导入时间从几十秒压缩到几秒内。
示例代码(以Stock导入为例):
public function insertFromFile(string $fileName) { $conn = $this->getEntityManager()->getConnection(); $conn->getConfiguration()->setSQLLogger(null); // 关闭SQL日志减少开销 $batchSize = 500; // 一次插入500条,可根据数据库性能调整 $rows = []; $handle = fopen($fileName, 'r'); fgetcsv($handle); // 跳过表头 while (($row = fgetcsv($handle)) !== false) { $rows[] = [ $row[0], // asin $row[1], // productName (int)$row[2] // quantity ]; if (count($rows) === $batchSize) { $conn->executeQuery( 'INSERT INTO stock (asin, product_name, afn_warehouse_quantity) VALUES (?, ?, ?)', $rows, [\PDO::PARAM_STR, \PDO::PARAM_STR, \PDO::PARAM_INT] ); $rows = []; } } // 插入剩余数据 if (!empty($rows)) { $conn->executeQuery( 'INSERT INTO stock (asin, product_name, afn_warehouse_quantity) VALUES (?, ?, ?)', $rows, [\PDO::PARAM_STR, \PDO::PARAM_STR, \PDO::PARAM_INT] ); } }
同样的逻辑可以复制到StorageFee和Sales的导入中,原生SQL的批量插入效率远高于ORM。
亚马逊ASIN本身就是全局唯一的业务标识符,完全可以用它来替代自增ID作为主键,这样能进一步简化数据模型,减少冗余字段。
修改StorageFee实体:
/** @ORM\Entity(repositoryClass="App\Repository\StorageFeeRepository") */ class StorageFee { /** * @ORM\Id() * @ORM\Column(type="string", length=16) */ private $asin; // 直接用ASIN做主键 /** @ORM\Column(type="string", length=8) */ private $currency; /** @ORM\Column(type="float") */ private $estimatedMonthlyStorageFee; // 构造函数初始化ASIN public function __construct(string $asin) { $this->asin = $asin; } // getter/setter... }
此时Stock和StorageFee的关联会更直接,因为主键就是业务标识,不需要额外的关联字段映射。字符串主键的索引性能和整数主键差异极小,完全不用担心性能问题。
如果因为某些原因不能用原生SQL导入,那至少要把循环更新改成单条JOIN更新,一次性完成所有关联:
public function updateAssociation() { $conn = $this->getEntityManager()->getConnection(); $conn->executeQuery(' UPDATE stock s INNER JOIN storage_fee sf ON s.asin = sf.asin SET s.storageFeeId = sf.id '); }
这条SQL会在数据库层面完成所有关联,耗时仅需几毫秒,彻底解决循环更新的性能问题。
内容的提问来源于stack exchange,提问作者ABSimon

