Rails 3.2.13 の Associations でデッドロックが起こるかもしれない問題

データベースに InnoDBMySQL を用いた Rails アプリケーションで、次のような二つのクラスを用意します。

class Parent < ActiveRecord::Base
  attr_accessible :name
  has_many :children, :dependent => :destroy
end

class Child < ActiveRecord::Base
  attr_accessible :name
  belongs_to :parent
end

この状態で rails console からトランザクションを使ったデータ登録を試してみます。

Parent.transaction do
  parent = Parent.new
  parent.name = 'parent'
  parent.save
  child = Child.new
  child.parent = parent
  child.name = 'child'
  child.save
end

SQL のログを見ると次のように実行されています。

   (0.2ms)  BEGIN
  SQL (0.4ms)  INSERT INTO `parents` (`created_at`, `name`, `updated_at`) VALUES ('2013-05-17 06:42:43', 'parent', '2013-05-17 06:42:43')
  SQL (0.3ms)  INSERT INTO `children` (`created_at`, `name`, `parent_id`, `updated_at`) VALUES ('2013-05-17 06:42:43', 'child', 2, '2013-05-17 06:42:43')
   (0.6ms)  COMMIT

次に、今作ったデータを :dependent => :destroy を利用して一気に消してみます。

parent = Parent.first
parent.destroy

こちらはこんな感じ。

   (0.2ms)  BEGIN
  Child Load (0.5ms)  SELECT `children`.* FROM `children` WHERE `children`.`parent_id` = 2
  SQL (0.3ms)  DELETE FROM `children` WHERE `children`.`id` = 2
  SQL (0.2ms)  DELETE FROM `parents` WHERE `parents`.`id` = 2
   (0.8ms)  COMMIT

この段階で嫌な予感がした方がいるかもしれませんが、2つのコンソールから MySQL の対話型インターフェースにアクセスし、次のような順番で先ほどの SQL を実行していきます。

BEGIN; -- transaction 1
BEGIN; -- transaction 2
INSERT INTO `parents` (`created_at`, `name`, `updated_at`) VALUES ('2013-05-17 06:42:43', 'parent', '2013-05-17 06:42:43'); -- transaction 1
DELETE FROM `children` WHERE `children`.`id` = 2; -- transaction 2
DELETE FROM `parents` WHERE `parents`.`id` = 2 -- transaction 2
INSERT INTO `children` (`created_at`, `name`, `parent_id`, `updated_at`) VALUES ('2013-05-17 06:42:43', 'child', 2, '2013-05-17 06:42:43'); -- transaction 1

COMMIT は書いていませんが、おそらく最後の INSERT を実行した段階で、そのターミナルに返答が無くなるはずです。それぞれの機能自体はおかしい事をしているわけではありませんが、このような原因でバグが発生する事もあるかもしれないので、備忘録的にメモしておきます。