doctrine2 - 如何提高flush的效率?

13
我必须更新我的Doctrine实体以匹配(潜在非常大的)XML文件中的记录。我还必须根据XML中的数据更新ManyToMany关联。这是我在循环内部所做的事情:
  1. 从XML获取数据
  2. 从数据库获取实体(如果不存在则创建新实体)
  3. 设置新实体属性
  4. 获取当前实体关联(getter返回ArrayCollection对象)
  5. 清除所有关联(通过调用ArrayCollection :: clear())
  6. 设置新关联(通过在子循环中调用ArrayCollection :: add())
  7. 通过EntityManager持久化实体
循环后,我调用EntityManager :: flush()。
问题是刷新会生成大量查询,而不是一次性更新/插入/删除多个行。对于每个实体执行以下查询:
  • SELECT以从数据库获取实体
  • UPDATE以更新实体属性(目前跳过此步骤,因为尚未更改属性...)
  • DELETE以清除先前的关联
  • INSERT以插入新关联
所以对于305个XML记录,我获得915个查询(如果所有实体都更改,则可能达到1220个查询),这使导入变得非常缓慢。
我可以利用IdentityMap并在循环之前预取实体,但仍然存在UPDATE / DELETE / INSERT查询。
  • 有没有办法让flush方法更好地优化查询(使用multi-insert,WHERE IN而不是多个DELETE查询等)?
  • 这是刷新方法的正常行为还是我做错了什么?
  • 也许我更新实体关联的方式存在问题。是否有更好的方法来解决此问题? (而不是“获取/清除/添加”方法)
  • 我知道Doctrine不适用于大规模批处理,但我认为将其用于XML导入是避免使用非ORM方法可能出现的数据库不一致性的最佳方法。是这样吗?
  • 如果上述方法不正确,我应该如何解决这个问题?
  • 2个回答

    35
    您做得很好 - 只是慢了,因为ORM的额外抽象意味着您无法进行所需的优化。话虽如此,EntityManager在大事务上确实变得缓慢。如果不是绝对需要将它们全部放在一个大事务中,您可以通过在循环的每20-200次迭代后flush()和clear() EntityManager来获得更高效的代码。如果这不能满足您的性能需求,我想到的唯一替代方法是恢复运行针对您的DBMS的自定义SQL的自定义代码。我知道这不是一个好答案,但至少我可以告诉您,您没有疯狂。------编辑------从批处理的官方Doctrine2文章中:
    有些人好像在想为什么Doctrine不使用多次插入(insert into (...) values (...), (...), (...), ...)
    首先,这种语法只适用于mysql和更新的postgresql版本。其次,在使用AUTO_INCREMENT或SERIAL时,没有简单的方法来获取此类多次插入中生成的所有标识符,并且ORM需要标识符以管理对象的标识。最后,对于ORM,插入性能很少是瓶颈。普通插入对于大多数情况已经足够快了,如果你真的想要快速进行批量插入,那么多次插入不是最好的方式,即Postgres COPY或Mysql LOAD DATA INFILE速度更快。
    这些是ORM中不值得实现在mysql和postgresql上执行多次插入的抽象的原因。
    此外,在使用本地数据库和远程数据库时,性能存在显著差异,因为将每个查询发送到远程服务器的开销相当大。而使用本地数据库时,由于事务和数据库优化,开销要低得多。(例如,在问题示例中,时间从70秒降低到300毫秒)

    4
    根据我的研究,如果仍然使用ORM,就没有更好的方法来优化导入。我已经为可能未来的谷歌搜索者添加了一些信息。 - Petr Peller
    1
    在添加 $em->clear() 后,持久化速度快了很多,完全解决了我的问题。如果您想要更快的插入速度,请禁用每个插入查询时的索引重建。$conn->prepare('SET autocommit=0;')->execute(),并在查询之后执行 $conn->prepare('COMMIT;')->execute(); - tomazahlin
    1
    事实上,连接类支持显式处理事务:http://doctrine-orm.readthedocs.org/en/latest/reference/transactions-and-concurrency.html#approach-2-explicitly - timdev

    5

    我不确定这是否直接回答了原帖提出的问题,但希望这可以帮助其他遇到Doctrine刷新速度问题的人。

    ...关于刷新速度,请确保您的xdebug分析器没有开启。

    [php.ini]
    ; PROFILING
    ;xdebug.profiler_enable = 1
    ;xdebug.profiler_output_name = "cachegrind.out.%t.%s.%p"
    ;xdebug.profiler_output_dir = "C:\xampp\tmp"
    

    举个例子,这对我的Doctrine flush操作产生了多大影响。处理3000条记录时需要55秒,而关闭分析器后只需要5秒钟!


    1
    这应该是一个合适的答案,在我看来。禁用xdebug起了很大的作用,从几个小时到几分钟。谢谢。 - Janusz Grabis

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接