> http://emphaticsolutions.com/2009/11/23/has_many_through_across_databases.html
> http://blog.arkency.com/2013/12/rails4-preloading/@H_502_4@
我的项目正在转向拥有多个数据库(目前在同一台服务器上),我希望能够在这些数据库之间加入.为了做到这一点,我需要将数据库名称添加到表前缀,如下所示:@H_502_4@
class FirstBase < ActiveRecord::Base def self.table_name_prefix "DBNAME.t_" end establish_connection :firstdb end class User < FirstBase has_many :user_roles end class UserRole < FirstBase belongs_to :user end
添加表名称前缀似乎会影响包含在同一查询上的默认行为,即使在同一数据库中也是如此.考虑User.includes(:user_roles).first@H_502_4@
没有表名前缀:@H_502_4@
User Load (67.1ms) SELECT
t_users
.* FROMt_users
LIMIT 1
UserRole Load (84.5ms) SELECTt_user_roles
.* FROMt_user_roles
WHERE
t_user_roles
.user_id
IN (1)@H_502_4@
使用表名前缀:@H_502_4@
sql (76.8ms) SELECT DISTINCT
DBNAME
.t_users
.id FROMDBNAME
.t_users
LEFT OUTER JOINDBNAME
.t_user_roles
ONDBNAME
.t_user_roles
.user_id
=DBNAME
.t_users
.id
LIMIT 1@H_502_4@sql (66.4ms) SELECT
DBNAME
.t_users
.id
AS t0_r0,DBNAME
.t_users
.DBNAME
.t_user_roles
.id
AS t1_r0,DBNAME
.t_user_roles
.user_id
AS t1_r1 FROMDBNAME
.t_users
LEFT OUTER JOINDBNAME
.t_user_roles
ONDBNAME
.t_user_roles
.user_id
=DBNAME
.t_users
.id
WHEREDBNAME
.t_users
.id
IN (1)@H_502_4@
换句话说,调用包含的默认行为已经从预加载更改为热负载.@H_502_4@
有人知道为什么默认行为正在改变吗?必须有一些关于添加数据库名称,使Rails认为我们必须加载,但我不明白为什么.我也很惊讶地看到这一点,因为我想象的是添加数据库名称是不寻常的.我可以通过更改所有包含预加载来强制将其修复到我们的代码库中,但我想了解这里发生了什么.有没有办法改变默认行为?@H_502_4@
解决方法
在ActiveRecord::Relation,exec_queries调用eager_loading?来决定是否应该加载.这调用references_eager_loaded_tables?它使用tables_in_string尝试在SQL查询中查找不是连接表的一部分的表名:@H_502_4@
# ActiveRecord::Relation#references_eager_loaded_tables? (tables_in_string(to_sql) - joined_tables).any?
tables_in_string方法有缺陷,因为它并不总是正确解析sql.该代码可用于查看SQL查询中表名的内容:@H_502_4@
relation = User.includes(:user_roles) relation.send(:tables_in_string,relation.to_sql)
使用DBNAME.t_表名前缀,这将给出[“DBNAME”,“t_users”]作为表名,这是错误的.它应该给[“DBNAME.t_users”].@H_502_4@
ActiveRecord query changing when a dot/period is in condition value记录了类似的问题.这导致ActiveRecord::Relation的变化在决定是预加载还是加载负载时,不再使用tables_in_string.@H_502_4@