优化Rails 4 / ActiveRecord / MySQL中的删除操作

问题描述 投票:0回答:3

本质上,我们需要删除给定

T1
没有与之关联的
t3
记录的
@user
记录。虽然不是 必需,但最好也删除没有
T2
连接的
T3
记录。

这是已投入生产的代码。显然,这很棒,因为它通过了单元测试(哈!)...除了它会导致生产中数百万行的锁定,当多个用户点击 时,会导致服务器 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. 使用 ActiveRecord 根据 Rails 中的联接值从一个或多个表删除记录的最佳方法是什么?
  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
3个回答
1
投票

使用

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

详细信息(这是为

DELETE
编写的,但可以改编为
UPDATE
。)

如果不需要,请勿使用

LEFT

确保您有合适的索引以避免在

JOINs
上进行表扫描。

如果其中任何一个表很多:很多,请按照效率提示此处 .


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
函数来解释任何查询。


0
投票

作为一个快速(可能是永久性的)修复,我决定将

.delete_all
替换为
.destroy_all
,这实际上只会运行内部查询,扫描 27 行,然后逐条实例化并删除记录,还有运行的额外优势
destroy
如果需要回调。

LEFT JOIN
是查找此时由于其他表中的子记录从未创建而无效的记录。范围内的记录数不应超过 30 条。代码尝试查找并删除不应存在且通常不存在的记录。这意味着,95% 的情况下,代码片段将在(快速)初始查询返回空后退出。

按照 ruby_newbie 的建议运行

.to_sql
对于解决这个问题非常有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.