2013-05-21 143 views
1

我有一个简单的Rails应用程序的文章和评论在MySQL 5.5和Ruby 1.9.3运行和轨道3.2.12:为什么ActiveRecord destroy_all需要这么久?

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

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

我已经产生了大量的评论的一篇文章,而现在想删除他们都在铁轨控制台:

$ 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_alldependent: :destroydependent: :delete_all显示出非常相似的行为。

流行的看法似乎是,destroy_all的问题在于它实例化所有对象并逐个删除它们,但它看起来不像是这里的问题。在所有的DELETEs被执行之后以及在COMMIT最终被调用之前需要花费这么长的时间?

回答

1

深入研究这个问题,它似乎是从comments阵列中删除需要很长时间。然后从阵列here中删除被删除的记录。

与大阵模拟,我们得到同样的缓慢行为:

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#cleardestroy_all的情况下...

+0

看看我更新的答案,看看它是否结合上述多一点光。 – TreyE

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个删除语句,包含在一个事务中,或者不是,仍然需要很长时间。

+0

如果你读过我我仔细地问了一下,我不是在询问多个“DELETE”,而是在所有这些“DELETE”之后和“COMMIT”之前究竟发生了什么。正如我所说的,关联中的“delete_all”表现出相同的行为。 'Comment.delete_all'显然会更快,因为它不会在关联上执行。 –

0

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

当您更新/在一个事务中删除行,ActiveRecord的跟踪你已经修改了所有行的,所以它可以调用任何后提交定义挂钩(即使没有)。在过去,我发现这个簿记可能会很慢,当记录大量参与(几千个)。这个命中就像rails正在提交事务一样。

缓慢的位如果我的记忆是正确的罪魁祸首是铁轨号码uniq对已更改对象的数组。如何==hash实现的细节似乎使在某些情况下

在过去慢,我已经通过

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

步履蹒跚这里面当然休息的承诺回调(你可能不无论如何使用)

+0

这样玩更多一点,它看起来并不完全是导致延迟的回调,而是记录从巨大的关联数组中移除的方式。 –