【问题标题】:Optimizing delete operations in Rails 4 / ActiveRecord / MySQL在 Rails 4 / ActiveRecord / MySQL 中优化删除操作
【发布时间】:2018-02-07 19:18:02
【问题描述】:

本质上,对于给定的@user,我们需要删除没有关联t3 记录的T1 记录。虽然不是必需,但最好删除没有T3 连接的T2 记录。

这是被推送到生产环境的代码。显然,这很棒,因为它通过了单元测试(哈哈!)...除了它在生产中导致数百万行锁定,导致服务器 500 死锁Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction ) 当多个用户同时点击DELETE 查询时。 是的,索引已经到位

T1.where(user_id: @user.id, enabled: true)
    .joins('LEFT JOIN t2 ON t2.t1_id = t1.id')
    .joins('LEFT JOIN t3 ON t3.id = t2.t3_id')
    .where('t3.id IS NULL').delete_all

生成的 SQL:

DELETE FROM `t1`
WHERE `t1`.`id` IN
(SELECT id FROM
     (SELECT `t1`.`id` FROM `t1`
      LEFT JOIN t2 ON t2.t1_id = t1.id
      LEFT JOIN t3 ON t3.id = t2.t3_id
      WHERE `t1`.`user_id` = 65987
      AND `t1`.`enabled` = 1
      AND (t2.id IS NULL)
     ) __active_record_temp
    );

我知道这是生成的 SQL 的唯一原因是它包含在 Server 500 死锁错误中。 在测试时,我似乎无法在控制台中显示 delete_all 查询。 我能够获取查询输出并将其转换为带有解释的 SELECT,它显示了最外层的选择扫描数百万行(我相信这意味着DELETE 操作的行锁数量相同。)最里面的查询只扫描 27 行。

问题:

  1. 根据 Rails 中的 ActiveRecord 连接值,从一个或多个表中删除记录的最佳方法是什么?
  2. 我们必须在 Rails 中查看/测试 SQL 输出以确保我不会面临性能不佳和死锁的风险?

更新:情节变厚...添加当前关联

class User < ActiveRecord::Base
has_many :T1s
has_many :T2s

class T1 < ActiveRecord::Base
belongs_to :user

class T2Custom < ActiveRecord::Base
self.table_name = "t2"
has_many :T3s, :foreign_key => :t2_id

class T3 < ActiveRecord::Base
belongs_to :T2, foreign_key: "t2_id"
belongs_to :T1

【问题讨论】:

    标签: mysql ruby-on-rails ruby-on-rails-4 activerecord innodb


    【解决方案1】:

    1) 您可以使用 Active Record (https://apidock.com/rails/ActiveRecord/Batches/find_in_batches) find_in_batches 方法删除记录,并且是可配置的。

    2) 您可以在测试中使用 Bullet Gem 来验证您没有 n+1 个查询(https://github.com/flyerhzm/bullet),但我不确定这是您的问题。

    您可以使用 Active Record (https://apidock.com/rails/ActiveRecord/Relation/to_sql) .to_sql 函数来解释任何查询。

    3) 如果您的问题仍然需要帮助,您可以发布更多代码,也许是生成查询的代码、关联等。 https://stackoverflow.com/help/how-to-ask

    【讨论】:

      【解决方案2】:

      使用PRIMARY KEY 浏览主表。对于每块(例如 1000 行),在单独的事务中执行 UPDATE

      Details(这是为DELETE 编写的,但可以改编为UPDATE。)

      如果不需要,请不要使用LEFT

      确保您有合适的索引以避免JOINs 上的表扫描。

      如果其中任何一个表是 many:many,请遵循效率提示here

      【讨论】:

        【解决方案3】:

        作为一个快速(可能是永久性的)修复,我决定用 .destroy_all 替换 .delete_all,这实际上只会运行内部查询,扫描 27 行,然后逐一实例化和删除记录,还有额外的优势如果需要,运行destroy 回调。

        LEFT JOIN 用于查找此时由于其他表中的子记录从未创建过而无效的记录。范围内的记录数不应超过 30。代码试图查找和删除不应存在且通常不存在的记录。这意味着,95% 的时间,sn-p 将在(快速)初始查询返回为空后退出。

        按照 ruby​​_newbie 的建议运行 .to_sql 对解决这个问题非常有帮助。

        【讨论】:

        • 代码已经在生产环境中完美运行了大约 4 个月。
        猜你喜欢
        • 2023-01-30
        • 2014-08-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-06
        • 2014-01-09
        • 2010-12-31
        相关资源
        最近更新 更多