Rails 3 添加数据库名称到 table 使包含默认为预加载

Rails 3 adding database name to table makes includes default to eager load

来源:

我的项目正朝着拥有多个数据库(目前在同一台服务器上)的方向发展,我希望能够在这些数据库之间加入。为此,我需要将数据库名称添加到 table 前缀,如下所示:

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

添加 table 名称前缀似乎会影响 include 在同一查询中的默认行为,即使在同一数据库中也是如此。考虑 User.includes(:user_roles).first

没有table名字前缀:

User Load (67.1ms) SELECT t_users.* FROM t_users LIMIT 1 UserRole Load (84.5ms) SELECT t_user_roles.* FROM t_user_roles WHERE t_user_roles.user_id IN (1)

带table名字前缀:

SQL (76.8ms) SELECT DISTINCT DBNAME.t_users.id FROM DBNAME.t_users LEFT OUTER JOIN DBNAME.t_user_roles ON DBNAME.t_user_roles.user_id = DBNAME.t_users.id LIMIT 1

SQL (66.4ms) SELECT DBNAME.t_users.id AS t0_r0,DBNAME.t_users.email AS t0_r1, DBNAME.t_user_roles.id AS t1_r0, DBNAME.t_user_roles.user_id AS t1_r1 FROM DBNAME.t_users LEFT OUTER JOIN DBNAME.t_user_roles ON DBNAME.t_user_roles.user_id = DBNAME.t_users.id WHERE DBNAME.t_users.id IN (1)

换句话说,调用 include 的默认行为已从预加载更改为预加载。

有谁知道为什么默认行为会改变?一定有一些关于添加数据库名称的内容使 Rails 认为我们必须预先加载,但我不明白为什么。我也很惊讶地看到这一点,因为我认为添加数据库名称并不罕见。我可以通过将所有包含更改为预加载来在我们的代码库中强制修复此问题,但我想了解这里发生了什么。有没有办法改变默认行为?

问题是 table_name_prefix 引入了句点。这混淆了试图确定它是否应该预加载或预加载的逻辑。这是一个 Rails 3 错误,已在 Rails 4 中解决。如果您需要 Rails 3 中的特定行为,则需要明确指定 preloadeager_load 正如您在问题中指出的那样。

ActiveRecord::Relation 中,exec_queries 调用 eager_loading? 来决定是否应该预先加载。这会调用 references_eager_loaded_tables?,它使用 tables_in_string 尝试在 SQL 查询中查找不属于已连接 table 的 table 名称:

# ActiveRecord::Relation#references_eager_loaded_tables?
(tables_in_string(to_sql) - joined_tables).any?

tables_in_string 方法存在缺陷,因为它并不总是正确解析 SQL。此代码可用于查看它认为 SQL 查询中的 table 名称:

relation = User.includes(:user_roles)
relation.send(:tables_in_string, relation.to_sql)

使用 DBNAME.t_ table 名称前缀,这将给 ["DBNAME", "t_users"] 作为 table 名称,这是错误的。它应该给出 ["DBNAME.t_users"].

ActiveRecord query changing when a dot/period is in condition value. This led to changes in ActiveRecord::Relation 中记录了一个类似的问题,在决定是预加载还是预先加载时不再使用 tables_in_string