1

我有一个简单的 Rails 应用程序,其中包含在 MySQL 5.5、Ruby 1.9.3 和 rails 3.2.12 上运行的文章和评论:

class Article < ActiveRecord::Base                                                                                   
  attr_accessible :body, :title
  has_many :comments
end   

class Comment < ActiveRecord::Base
  attr_accessible :content
  belongs_to :article
end

我为一篇文章生成了很多评论,现在正试图在 rails 控制台中将它们全部删除:

$ rails c 
Loading development environment (Rails 3.2.12)
[1] pry(main)> a = Article.find(1)
   (2.0ms)  SET SQL_AUTO_IS_NULL=0
  Article Load (8.0ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`id` = 1 LIMIT 1
=> #<Article id: 1, title: "Test", body: "---\n- Est vel provident. Laboriosam dolor asperiore...", created_at: "2013-05-17 09:54:54", updated_at: "2013-05-21 14:52:18">
[2] pry(main)> require 'benchmark'
[3] pry(main)> puts Benchmark.measure { a.comments.destroy_all }
  Comment Load (896.0ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`article_id` = 1
  EXPLAIN (2.0ms)  EXPLAIN SELECT `comments`.* FROM `comments` WHERE `comments`.`article_id` = 1
EXPLAIN for: SELECT `comments`.* FROM `comments`  WHERE `comments`.`article_id` = 1
+----+-------------+----------+------+---------------+------------+---------+-------+-------+-------------+
| id | select_type | table    | type | possible_keys | key        | key_len | ref   | rows  | Extra       |
+----+-------------+----------+------+---------------+------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | comments | ref  | article_id    | article_id | 5       | const | 48186 | Using where |
+----+-------------+----------+------+---------------+------------+---------+-------+-------+-------------+
1 row in set (0.00 sec)

  SQL (1.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 2
  SQL (2.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 3
  SQL (1.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 4
  SQL (1.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 5
  SQL (1.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 6
  SQL (5.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 7
  SQL (2.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 8
  SQL (2.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 9

. . .
  SQL (0.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 37360
  SQL (0.0ms)  DELETE FROM `comments` WHERE `comments`.`id` = 37361

最后一个查询是删除最后一条评论,然后该过程会挂在那里长时间,然后才最终返回并提交:

   (1.9ms)  COMMIT
690.380000   1.390000 691.770000 (693.885877)

SHOW PROCESSLIST确认没有锁:

mysql> show processlist;
+----+----------+-----------+------------------+---------+------+-------+------------------+
| Id | User     | Host      | db               | Command | Time | State | Info             |
+----+----------+-----------+------------------+---------+------+-------+------------------+
|  6 | bloguser | localhost | blog_development | Query   |    0 | NULL  | show processlist |
|  7 | bloguser | localhost | blog_development | Sleep   |  459 |       | NULL             |
+----+----------+-----------+------------------+---------+------+-------+------------------+
2 rows in set (0.00 sec)

delete_all具有dependent: :destroydependent: :delete_all表现出非常相似的行为。

普遍的看法似乎是,问题destroy_all在于它实例化了所有对象并一个一个地删除它们,但看起来这不是问题所在。DELETE在所有的s 都被执行之后,以及 beforeCOMMIT最终被调用之后,需要这么长时间来处理是什么?

4

3 回答 3

1

深入研究,似乎是从comments数组中删除需要很长时间。然后从此处的数组中删除被删除的记录。

用一个大数组模拟这个,我们得到同样的缓慢行为:

1.9.3-p194 :001 > require 'benchmark'; require 'ostruct'
 => true 
1.9.3-p194 :002 > i = 0; a = []
 => [] 
1.9.3-p194 :003 > 35_000.times { i+=1; a << OpenStruct.new(value: i) }
 => 35000 
1.9.3-p194 :004 > puts Benchmark.measure { a.each { |i| a.delete(i) } }
623.560000   0.820000 624.380000 (625.244664)

ActiveRecord 可能会被优化为在...Array#clear的情况下执行destroy_all

于 2013-05-22T11:02:42.240 回答
0

请注意,#destroy_all实例化对象的每个实例,然后运行并删除它。这可能需要相当长的时间,这就是为什么你会得到所有这些不同DELETE的陈述而不是一个单一的陈述。你可能想要的是delete_all

Comment.delete_all("article_id = 1")

我知道您已经提到了实例化问题,但请同时尝试这两种不同的方法 - 我想您会看到不同之处。

上面的重要部分是您没有通过关联执行此操作的事实,请注意我提供的代码不执行此操作:

Article.find(1).comments.delete_all

它直接从评论中调用。这确保您没有实例化对象。通过关联代理调用 delete_all 会导致事物被实例化。如果它们被实例化,通常在删除/销毁它们时会收到回调 - 更不用说 ruby​​ 必须在内存中的集合中随机播放对象。

时间的原因是 ruby​​ 处理一个包含 35k 复杂关联对象的数组。同时,请注意 35k 删除语句。35,000 个删除语句,无论是否包含在事务中,仍然需要很长时间。

于 2013-05-21T22:54:34.573 回答
0

除了首先实例化所有行的事实之外destroy_all,这听起来像是 activerecord 的提交后回调。

当您更新/删除事务中的行时,activerecord 会跟踪您已修改的所有行,以便它可以调用任何定义的提交后挂钩(即使没有)。过去,我发现当涉及大量记录(几千条)时,这种簿记可能会非常慢。这个命中发生在 rails 提交事务的时候。

如果我的记忆是正确的罪魁祸首,那么缓慢的一点是 rails 调用uniq了已更改对象的数组。==在某些情况下,如何实施和实施的细节hash似乎使速度变慢

在过去,我通过这个步履蹒跚

class Foo  < ActiveRecord::Base
  #hobble commit hooks
  def add_to_transaction
  end
end

这当然会破坏提交回调(无论如何你可能都不会使用)

于 2013-05-21T23:00:52.550 回答