I have a simple rails app with articles and comments running on MySQL 5.5, Ruby 1.9.3 and 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
I have generated lots of comments for an article, and am now trying to delete them all in the rails console:
$ 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
The last query is deleting the last comment, and the process then hangs there for a very long time before finally returning and committing:
(1.9ms) COMMIT
690.380000 1.390000 691.770000 (693.885877)
SHOW PROCESSLIST
confirms that there is no lock:
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
with dependent: :destroy
or dependent: :delete_all
show a very similar behaviour.
The popular belief seems to be that the issue with destroy_all
is that it instantiates all the objects and deletes them one by one, but it doesn't look like it's the problem here. What is taking so long to process after all the DELETE
s have been executed, and before COMMIT
is finally called?