跳至内容 跳至搜索

Active Record 连接处理

方法
C
E
L
P
R
S
W

常量

DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
 
RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
 

Attributes

[W] connection_specification_name

实例公共方法

clear_query_caches_for_current_thread()

清除当前线程所有连接的查询缓存。

# File activerecord/lib/active_record/connection_handling.rb, line 261
def clear_query_caches_for_current_thread
  connection_handler.each_connection_pool do |pool|
    pool.clear_query_cache
  end
end

connected?()

如果 Active Record 已连接,则返回 true

# File activerecord/lib/active_record/connection_handling.rb, line 354
def connected?
  connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
end

connected_to(role: nil, shard: nil, prevent_writes: false, &blk)

在块的持续时间内连接到指定的角色(例如,读、写或自定义角色)和/或分片。块结束时,连接将返回到原始角色/分片。

如果仅传递了角色,Active Record 将根据请求的角色查找连接。如果请求了未建立的角色,将引发 ActiveRecord::ConnectionNotEstablished 错误。

ActiveRecord::Base.connected_to(role: :writing) do
  Dog.create! # creates dog using dog writing connection
end

ActiveRecord::Base.connected_to(role: :reading) do
  Dog.create! # throws exception because we're on a replica
end

切换到分片时,必须同时传递角色。如果传递了不存在的分片,将引发 ActiveRecord::ConnectionNotEstablished 错误。

当传递分片和角色时,Active Record 将首先查找角色,然后通过分片键查找连接。

ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
  Dog.first # finds first Dog record stored on the shard one replica
end
# File activerecord/lib/active_record/connection_handling.rb, line 137
def connected_to(role: nil, shard: nil, prevent_writes: false, &blk)
  if self != Base && !abstract_class
    raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
  end

  if !connection_class? && !primary_class?
    raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
  end

  unless role || shard
    raise ArgumentError, "must provide a `shard` and/or `role`."
  end

  with_role_and_shard(role, shard, prevent_writes, &blk)
end

connected_to?(role:, shard: ActiveRecord::Base.default_shard)

如果角色是当前连接的角色和/或当前连接的分片,则返回 true。如果没有传递分片,将使用默认值。

ActiveRecord::Base.connected_to(role: :writing) do
  ActiveRecord::Base.connected_to?(role: :writing) #=> true
  ActiveRecord::Base.connected_to?(role: :reading) #=> false
end

ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one) do
  ActiveRecord::Base.connected_to?(role: :reading, shard: :shard_one) #=> true
  ActiveRecord::Base.connected_to?(role: :reading, shard: :default) #=> false
  ActiveRecord::Base.connected_to?(role: :writing, shard: :shard_one) #=> true
end
# File activerecord/lib/active_record/connection_handling.rb, line 256
def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
  current_role == role.to_sym && current_shard == shard.to_sym
end

connected_to_all_shards(role: nil, prevent_writes: false, &blk)

将块传递给 connected_to 以处理模型配置的所有分片(如果有),并将结果以数组形式返回。

可选地,可以传递 role 和/或 prevent_writes,它们将被转发到每个 connected_to 调用。

# File activerecord/lib/active_record/connection_handling.rb, line 189
def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
  shard_keys.map do |shard|
    connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
  end
end

connected_to_many(*classes, role:, shard: nil, prevent_writes: false)

将角色和/或分片连接到提供的连接名称。可选地,可以传递 prevent_writes 来阻止对连接的写入。reading 将自动将 prevent_writes 设置为 true。

connected_to_many 是嵌套层级较深的 connected_to 块的替代方案。

用法

ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
  Dog.first # Read from animals replica
  Dinner.first # Read from meals replica
  Person.first # Read from primary writer
end
# File activerecord/lib/active_record/connection_handling.rb, line 166
def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
  classes = classes.flatten

  if self != Base || classes.include?(Base)
    raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base."
  end

  prevent_writes = true if role == ActiveRecord.reading_role

  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
  begin
    yield
  ensure
    connected_to_stack.pop
  end
end

connecting_to(role: default_role, shard: default_shard, prevent_writes: false)

使用指定的连接。

此方法对于确保使用特定连接很有用。例如,在启动控制台时以只读模式启动。

不建议在请求中使用此方法,因为它不像 connected_to 那样会传递给块。

# File activerecord/lib/active_record/connection_handling.rb, line 202
def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
  prevent_writes = true if role == ActiveRecord.reading_role

  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
end

connection()

软弃用。请改用 with_connectionlease_connection

# File activerecord/lib/active_record/connection_handling.rb, line 277
    def connection
      pool = connection_pool
      if pool.permanent_lease?
        case ActiveRecord.permanent_connection_checkout
        when :deprecated
          ActiveRecord.deprecator.warn <<~MESSAGE
            Called deprecated `ActiveRecord::Base.connection` method.

            Either use `with_connection` or `lease_connection`.
          MESSAGE
        when :disallowed
          raise ActiveRecordError, <<~MESSAGE
            Called deprecated `ActiveRecord::Base.connection` method.

            Either use `with_connection` or `lease_connection`.
          MESSAGE
        end
        pool.lease_connection
      else
        pool.active_connection
      end
    end

connection_db_config()

返回关联连接的 db_config 对象。

ActiveRecord::Base.connection_db_config
  #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
    @name="primary", @config={pool: 5, timeout: 5000, database: "storage/development.sqlite3", adapter: "sqlite3"}>

仅用于读取。

# File activerecord/lib/active_record/connection_handling.rb, line 337
def connection_db_config
  connection_pool.db_config
end

connection_pool()

# File activerecord/lib/active_record/connection_handling.rb, line 345
def connection_pool
  connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard, strict: true)
end

connection_specification_name()

返回当前类或其父类的连接规范名称。

# File activerecord/lib/active_record/connection_handling.rb, line 319
def connection_specification_name
  if @connection_specification_name.nil?
    return self == Base ? Base.name : superclass.connection_specification_name
  end
  @connection_specification_name
end

connects_to(database: {}, shards: {})

将模型连接到指定的数据库。database 关键字接受一个包含 roledatabase_key 的哈希。

这将使用 database_key 查找数据库配置并建立与该配置的连接。

class AnimalsModel < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :primary_replica }
end

connects_to 也支持水平分片。水平分片 API 也支持读副本。您可以像这样将模型连接到分片列表

class AnimalsModel < ApplicationRecord
  self.abstract_class = true

  connects_to shards: {
    default: { writing: :primary, reading: :primary_replica },
    shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two }
  }
end

返回一个数据库连接数组。

# File activerecord/lib/active_record/connection_handling.rb, line 81
def connects_to(database: {}, shards: {})
  raise NotImplementedError, "`connects_to` can only be called on ActiveRecord::Base or abstract classes" unless self == Base || abstract_class?

  if database.present? && shards.present?
    raise ArgumentError, "`connects_to` can only accept a `database` or `shards` argument, but not both arguments."
  end

  connections = []

  @shard_keys = shards.keys

  if shards.empty?
    shards[:default] = database
  end

  self.default_shard = shards.keys.first

  shards.each do |shard, database_keys|
    database_keys.each do |role, database_key|
      db_config = resolve_config_for_connection(database_key)

      self.connection_class = true
      shard = shard.to_sym unless shard.is_a? Integer
      connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
    end
  end

  connections
end

establish_connection(config_or_env = nil)

建立与数据库的连接。接受一个哈希作为输入,其中必须指定 :adapter 键,其值为数据库适配器(小写)的名称,例如对于常规数据库(MySQL、PostgreSQL 等)。

ActiveRecord::Base.establish_connection(
  adapter:  "mysql2",
  host:     "localhost",
  username: "myuser",
  password: "mypass",
  database: "somedatabase"
)

SQLite 数据库的示例

ActiveRecord::Base.establish_connection(
  adapter:  "sqlite3",
  database: "path/to/dbfile"
)

也接受字符串作为键(例如用于从 YAML 解析)。

ActiveRecord::Base.establish_connection(
  "adapter"  => "sqlite3",
  "database" => "path/to/dbfile"
)

或 URL

ActiveRecord::Base.establish_connection(
  "postgres://myuser:mypass@localhost/somedatabase"
)

如果设置了 ActiveRecord::Base.configurations(Rails 会自动将 config/database.yml 的内容加载到其中),也可以将一个符号作为参数,表示配置哈希中的一个键。

ActiveRecord::Base.establish_connection(:production)

在发生错误时,可能会引发 AdapterNotSpecifiedAdapterNotFoundArgumentError 异常。

# File activerecord/lib/active_record/connection_handling.rb, line 50
def establish_connection(config_or_env = nil)
  config_or_env ||= DEFAULT_ENV.call.to_sym
  db_config = resolve_config_for_connection(config_or_env)
  connection_handler.establish_connection(db_config, owner_name: self, role: current_role, shard: current_shard)
end

lease_connection()

返回当前与类关联的连接。这也可以用于“借用”连接以进行与特定 Active Record 无关的数据库工作。连接将在整个请求或作业期间保持租用状态,直到调用 release_connection

# File activerecord/lib/active_record/connection_handling.rb, line 272
def lease_connection
  connection_pool.lease_connection
end

prohibit_shard_swapping(enabled = true)

在传递的块中禁止切换分片。

在某些情况下,您可能希望能够切换分片,但不允许嵌套调用 connected_toconnected_to_many 再次切换。这在您使用分片提供每个请求的数据库隔离时很有用。

# File activerecord/lib/active_record/connection_handling.rb, line 214
def prohibit_shard_swapping(enabled = true)
  prev_value = ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
  ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = enabled
  yield
ensure
  ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = prev_value
end

release_connection()

将当前租用的连接返回到池中。

# File activerecord/lib/active_record/connection_handling.rb, line 301
def release_connection
  connection_pool.release_connection
end

remove_connection()

# File activerecord/lib/active_record/connection_handling.rb, line 358
def remove_connection
  name = @connection_specification_name if defined?(@connection_specification_name)

  # if removing a connection that has a pool, we reset the
  # connection_specification_name so it will use the parent
  # pool.
  if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
    self.connection_specification_name = nil
  end

  connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
end

retrieve_connection()

# File activerecord/lib/active_record/connection_handling.rb, line 349
def retrieve_connection
  connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
end

shard_keys()

# File activerecord/lib/active_record/connection_handling.rb, line 379
def shard_keys
  connection_class_for_self.instance_variable_get(:@shard_keys) || []
end

shard_swapping_prohibited?()

确定分片切换当前是否被禁止。

# File activerecord/lib/active_record/connection_handling.rb, line 223
def shard_swapping_prohibited?
  ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
end

sharded?()

# File activerecord/lib/active_record/connection_handling.rb, line 383
def sharded?
  shard_keys.any?
end

while_preventing_writes(enabled = true, &block)

阻止写入数据库,无论角色如何。

在某些情况下,您可能希望阻止向数据库写入,即使您连接的是可以写入的数据库。 while_preventing_writes 将在块的持续时间内阻止向数据库写入。

此方法不提供与只读用户相同的保护,而是作为防止意外写入的保障措施。

有关此方法阻止的查询,请参阅 READ_QUERY

# File activerecord/lib/active_record/connection_handling.rb, line 238
def while_preventing_writes(enabled = true, &block)
  connected_to(role: current_role, prevent_writes: enabled, &block)
end

with_connection(prevent_permanent_checkout: false, &block)

从连接池签出连接,传递给块,然后将其签回。如果连接已通过 lease_connection 或对 with_connection 的父级调用租用,则将传递相同的连接。如果块内调用了 lease_connection,则连接不会被签回。如果块内调用了 connection,则除非 prevent_permanent_checkout 参数设置为 true,否则连接不会被签回。

# File activerecord/lib/active_record/connection_handling.rb, line 312
def with_connection(prevent_permanent_checkout: false, &block)
  connection_pool.with_connection(prevent_permanent_checkout: prevent_permanent_checkout, &block)
end