Rails 4.1/Arel 5.0でactiverecord-refresh_connectionなどを使ってコネクションを切断したとき、切断後にスキーマのキャッシュが使用されない問題があったので、メモしておきます。
再現方法
以下のような簡単なRackアプリで再現できます
# Gemfile: # source 'https://rubygems.org' # gem 'rack' # gem 'activerecord', '~> 4.1.0' # gem 'mysql2' # gem 'activerecord-refresh_connection' require 'active_record' require 'activerecord-refresh_connection' ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'test') $buf = [] ActiveSupport::Notifications.subscribe('sql.active_record') do |name, start, finish, id, payload| $buf << payload[:sql] end use ActiveRecord::ConnectionAdapters::RefreshConnectionManagement # CREATE TABLE `items` ( # `id` int(11) NOT NULL AUTO_INCREMENT, # `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, # `price` int(11) DEFAULT NULL, # `description` text COLLATE utf8_unicode_ci, # `created_at` datetime DEFAULT NULL, # `updated_at` datetime DEFAULT NULL, # PRIMARY KEY (`id`) # ) class Item < ActiveRecord::Base; end run Proc.new {|env| # スキーマキャッシュが使われる Item.count # スキーマキャッシュが使われない #Item.where(name: 'test').to_a sqls = $buf.dup $buf.clear ['200', {'Content-Type' => 'text/plain'}, [sqls.join("\n")]] }
このアプリをrackupしてlocalhost:9292にアクセスしたとき、Item.count
の場合、コネクションが切断されてもスキーマキャッシュが使われる—SHOW FULL SHOW FULL FIELDS
は発行されませんが、#Item.where(name: 'test').to_a
をuncommentすると、リクエストのたびにSHOW FULL SHOW FULL FIELDS
が発行されるようになります。
原因
Item.where(name: 'test').to_a
の場合、ArelがModelとは別にスキーマのキャッシュを保持することが原因です。
Arelのキャッシュは再接続で消えるので、再接続のたびにSHOW FULL SHOW FULL FIELDS
が発行されることになります。
ArelはASTをたどってSQLを生成するとき、String型の場合、Arel::Visitors::ToSql#quoteを呼び出します。quote()
はカラムの型に合わせて値をクォートで囲んだり囲まなかったりという動作をするので、スキーマ情報を取りに行くことになります。
ちなみに、さすがに無駄だと思ったのか、Arel 7.0ではそもそもカラムの型を取得しないように設計が変わっていました。Arel 6.0ではこの問題がまだありましたが、検証したのがちょっと前なのでバックポートされているかも知れません。
対応策
Modelのキャッシュがあったらそっちを使うプルリクエストを投げたのですが、リジェクトされてしまいました…
すでにArel 7.0では問題が再発しないことも考えると、あんまりがんばるのも微妙な気がしたので、とりあえずMonkey patchのgemを作成しました。
困っている人はそんなにはいないと思いますが、この問題をふんじゃったかたの助けになれば幸いです。