Rails ActiveRecord - 性能 - 如何销毁无效的 belongs_to 记录

Rails ActiveRecord - Performance - How to destroy invalid belongs_to records

我有两个模型:

不幸的是,创建迁移时,没有 foreign_key 添加到 Assethistories。存在一些资产历史记录,其中 Assethistories.asset_id 具有 Asset.id 中不存在的值(可能有人对资产 table 使用了删除而不是销毁)。

大约有 1000 万条资产记录和 2500 万条资产历史记录。

使用此查询需要很长时间:

Assethistory.where("asset_id NOT IN (select id from assets)").delete_all
or in rails syntax:
Assethistory.where.not(asset_id: Asset.select(:id)).delete_all

注意:我们可以使用 delete_all,因为没有回调或嵌套模型。

事实上,仅对无效记录执行 COUNT 个操作需要很长时间。

Assethistory.where.not(asset_id: Asset.select(:id)).count

有没有什么方法可以更高效地销毁无效记录?

我有一些数据比你的情况小 20 倍,例如 5M assethistory 和 500K asset 像你的情况,这可以通过使用 where.not 重现你的问题.通过遵循 Anti-Join 模式,其中加入不存在的记录,将为您带来您想要的结果和更好的性能。

Assethistory.left_outer_joins(:asset).where(assets: { id: nil })
# => 3500 rows (415.3ms)

Assethistory.where('NOT EXISTS (:assets)', assets: Asset.select('1').where('assets.id = assethistories.asset_id'))
# => 3500 rows (701.6ms)

通过使用 LEFT OUTER JOIN 而不是 WHERE NOT,数据库以一种非常不同的方式扫描 table。 WHERE NOT 遍历 assethistories 中的所有行,其中 assethistories.asset_id 不匹配所有现有的 assets.id.

LEFT OUTER JOIN 从 joined-table(assethistories and assets as a big one table) 中查询记录,其中行有 null 值在 asset.id 列中,效率更高。