我有一个简单的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: :destroy
的 dependent: :delete_all
表现出非常相似的行为。
流行的看法似乎是destroy_all
的问题是它实例化所有对象并逐个删除它们,但它看起来不像这里的问题。在执行了所有DELETE
之后,在最终调用COMMIT
之前需要花费这么长的时间来处理什么?
答案 0 :(得分: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)
如果Array#clear
...
destroy_all
答案 1 :(得分: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条删除语句仍然需要很长时间。
答案 2 :(得分:0)
除了destroy_all
首先实例化所有行之外,这听起来像提交回调后的activerecord。
当您更新/删除事务中的行时,activerecord会跟踪您修改过的所有行,以便它可以调用任何定义的提交挂钩(即使没有)。在过去,我发现当涉及大量记录时(几千个),这种簿记可能会非常缓慢。这个命中就像rails提交事务一样。
如果我的内存是正确的,那么缓慢的罪魁祸首是rails在更改的对象数组上调用uniq
。 ==
和hash
如何实施的详细信息似乎在某些情况下会变慢
在过去,我通过
蹒跚而行class Foo < ActiveRecord::Base
#hobble commit hooks
def add_to_transaction
end
end
当然会破坏提交回调(你可能还没有使用)