隨著應用程式越來越流行和使用,您需要擴充套件應用程式 支援您的新使用者及其資料。您的應用程式可能需要的一種方式 擴充套件是在資料庫級別。 Rails 現在支援多個數據庫 因此您不必將所有資料都儲存在一個地方。
目前支援以下功能:
- 多個作者資料庫和每個副本
- 您正在使用的 model 的自動連線切換
- 根據 HTTP 動詞在作者和副本之間自動交換 和最近的寫作
- 用於建立、刪除、遷移和與多個互動的 Rails 任務 資料庫
(尚)不支援以下功能:
*自動交換水平分片 * 負載均衡副本 * 為多個數據庫轉儲模式快取
1 設定您的應用程式
儘管 Rails 嘗試為您完成大部分工作,但您仍然需要執行一些步驟 需要做讓您的應用程式為多個數據庫做好準備。
假設我們有一個具有單個編寫器資料庫的應用程式,我們需要新增一個 我們正在新增的一些新表的新資料庫。新資料庫的名稱將是 “動物”。
database.yml
看起來像這樣:
production:
database: my_primary_database
adapter: mysql2
username: root
password: <%= ENV['ROOT_PASSWORD'] %>
讓我們為第一個配置新增一個副本,以及一個名為動物的第二個資料庫和一個
複製品也是如此。為此,我們需要將 database.yml
從 2 層更改為
到 3 層配置。
如果提供了主要配置,它將用作“預設”配置。如果
沒有名為 "primary"
的配置,Rails 會預設使用第一個配置
對於每個環境。預設配置將使用預設 Rails 檔名。例如,
主要配置將使用 schema.rb
作為架構檔案,而所有其他條目
將使用 [CONFIGURATION_NAMESPACE]_schema.rb
作為檔名。
production:
primary:
database: my_primary_database
username: root
password: <%= ENV['ROOT_PASSWORD'] %>
adapter: mysql2
primary_replica:
database: my_primary_database
username: root_readonly
password: <%= ENV['ROOT_READONLY_PASSWORD'] %>
adapter: mysql2
replica: true
animals:
database: my_animals_database
username: animals_root
password: <%= ENV['ANIMALS_ROOT_PASSWORD'] %>
adapter: mysql2
migrations_paths: db/animals_migrate
animals_replica:
database: my_animals_database
username: animals_readonly
password: <%= ENV['ANIMALS_READONLY_PASSWORD'] %>
adapter: mysql2
replica: true
使用多個數據庫時,有一些重要的設定。
首先,primary
和 primary_replica
的資料庫名稱應該相同,因為它們包含
相同的資料。 animals
和 animals_replica
也是這種情況。
其次,作者和副本的使用者名稱應該不同,並且 副本使用者的資料庫許可權應該設定為只讀而不是寫。
使用副本資料庫時,需要在副本中新增一個replica: true
條目
database.yml
。這是因為 Rails 否則無法知道哪個是副本
哪個是作者。 Rails 不會針對副本執行某些任務,例如 migrations。
最後,對於新的 writer 資料庫,您需要將 migrations_paths
設定為目錄
您將為該資料庫儲存 migrations 的位置。我們將更多地關注 migrations_paths
稍後在本指南中。
現在我們有了一個新的資料庫,讓我們建立連線 model。為了使用 新資料庫 我們需要建立一個新的抽象類並連線到動物資料庫。
class AnimalsRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :animals, reading: :animals_replica }
end
然後我們需要更新 ApplicationRecord
以瞭解我們的新副本。
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :primary_replica }
end
如果您為應用程式記錄使用不同名稱的類,則需要
改為設定 primary_abstract_class
,以便 Rails 知道 ActiveRecord::Base
是哪個類
應該共享一個連線。
class PrimaryApplicationRecord < ActiveRecord::Base
self.primary_abstract_class
end
連線到 primary/primary_replica 的類可以從您的主要抽象繼承 類標準 Rails 應用程式:
class Person < ApplicationRecord
end
預設情況下,Rails 期望主資料庫的資料庫角色是 writing
和 reading
和副本分別。如果你有一個遺留系統,你可能已經設定了角色
你不想改變。在這種情況下,您可以在應用程式配置中設定新角色名稱。
config.active_record.writing_role = :default
config.active_record.reading_role = :readonly
在單個 model 中連線到您的資料庫然後從該 model 繼承很重要 對於表,而不是將多個單獨的 models 連線到同一個資料庫。資料庫 客戶端對開啟連線的數量有限制,如果你這樣做,它會 乘以您擁有的連線數,因為 Rails 使用 model 類名作為 連線規範名稱。
現在我們已經設定了 database.yml
和新的 model,是時候建立資料庫了。
Rails 6.0 附帶了在 Rails 中使用多個數據庫所需的所有 Rails 任務。
您可以執行 bin/rails -T
來檢視您可以執行的所有命令。您應該看到以下內容:
$ bin/rails -T
rails db:create # Creates the database from DATABASE_URL or config/database.yml for the ...
rails db:create:animals # Create animals database for current environment
rails db:create:primary # Create primary database for current environment
rails db:drop # Drops the database from DATABASE_URL or config/database.yml for the cu...
rails db:drop:animals # Drop animals database for current environment
rails db:drop:primary # Drop primary database for current environment
rails db:migrate # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)
rails db:migrate:animals # Migrate animals database for current environment
rails db:migrate:primary # Migrate primary database for current environment
rails db:migrate:status # Display status of migrations
rails db:migrate:status:animals # Display status of migrations for animals database
rails db:migrate:status:primary # Display status of migrations for primary database
rails db:reset # Drops and recreates all databases from their schema for the current environment and loads the seeds
rails db:reset:animals # Drops and recreates the animals database from its schema for the current environment and loads the seeds
rails db:reset:primary # Drops and recreates the primary database from its schema for the current environment and loads the seeds
rails db:rollback # Rolls the schema back to the previous version (specify steps w/ STEP=n)
rails db:rollback:animals # Rollback animals database for current environment (specify steps w/ STEP=n)
rails db:rollback:primary # Rollback primary database for current environment (specify steps w/ STEP=n)
rails db:schema:dump # Creates a database schema file (either db/schema.rb or db/structure.sql ...
rails db:schema:dump:animals # Creates a database schema file (either db/schema.rb or db/structure.sql ...
rails db:schema:dump:primary # Creates a db/schema.rb file that is portable against any DB supported ...
rails db:schema:load # Loads a database schema file (either db/schema.rb or db/structure.sql ...
rails db:schema:load:animals # Loads a database schema file (either db/schema.rb or db/structure.sql ...
rails db:schema:load:primary # Loads a database schema file (either db/schema.rb or db/structure.sql ...
rails db:setup # Creates all databases, loads all schemas, and initializes with the seed data (use db:reset to also drop all databases first)
rails db:setup:animals # Creates the animals database, loads the schema, and initializes with the seed data (use db:reset:animals to also drop the database first)
rails db:setup:primary # Creates the primary database, loads the schema, and initializes with the seed data (use db:reset:primary to also drop the database first)
執行像 bin/rails db:create
這樣的命令將建立主資料庫和動物資料庫。
請注意,沒有用於建立資料庫使用者的命令,您需要手動執行此操作
支援您的副本的只讀使用者。如果你只想創造動物
資料庫你可以執行bin/rails db:create:animals
。
2 無需管理架構和 Migration 即可連線到資料庫
如果您想連線到沒有任何資料庫的外部資料庫
模式管理、migrations、種子等管理任務可以設定
每個資料庫的配置選項 database_tasks: false
。預設情況下是
設定為真。
production:
primary:
database: my_database
adapter: mysql2
animals:
database: my_animals_database
adapter: mysql2
database_tasks: false
3 發電機和 Migrations
多個數據庫的 Migrations 應該位於它們自己的資料夾中,字首為 配置中資料庫 key 的名稱。
您還需要在資料庫配置中設定 migrations_paths
來告訴 Rails
在哪裡可以找到 migrations。
例如,animals
資料庫將在 db/animals_migrate
目錄中查詢 migrations 並
primary
會在 db/migrate
中查詢。 Rails 生成器現在採用 --database
選項
以便在正確的目錄中生成檔案。該命令可以像這樣執行:
$ bin/rails generate migration CreateDogs name:string --database animals
如果您使用 Rails 生成器,則 scaffold 和 model 生成器將建立抽象 為你上課。只需將資料庫 key 傳遞到命令列即可。
$ bin/rails generate scaffold Dog name:string --database animals
將建立一個具有資料庫名稱和 Record
的類。在這個例子中
資料庫是 Animals
所以我們最終得到 AnimalsRecord
:
class AnimalsRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :animals }
end
生成的 model 會自動繼承自 AnimalsRecord
。
class Dog < AnimalsRecord
end
由於 Rails 不知道哪個資料庫是您的編寫器的副本,因此您需要 完成後將其新增到抽象類中。
Rails 只會生成一次新類。它不會被新的 scaffolds 覆蓋 如果 scaffold 被刪除,則刪除。
如果你已經有一個抽象類並且它的名字與 AnimalsRecord
不同,你可以透過
--parent
選項表明你想要一個不同的抽象類:
$ bin/rails generate scaffold Dog name:string --database animals --parent Animals::Record
這將跳過生成 AnimalsRecord
因為你已經向 Rails 表明你想要
使用不同的父類。
4 開啟自動連線切換
最後,為了在您的應用程式中使用只讀副本,您需要啟用 自動切換的中介軟體。
自動切換允許應用程式從寫入器切換到副本或副本 根據 HTTP 動詞以及請求使用者最近是否進行了寫入。
如果應用程式正在接收 POST、PUT、DELETE 或 PATCH 請求,應用程式將 自動寫入寫入器資料庫。在寫入後的指定時間內, 應用程式將從主讀取。對於 GET 或 HEAD 請求,應用程式將讀取 除非最近有寫入,否則來自副本。
要啟用自動連線切換中介軟體,請新增或取消註釋以下內容 應用程式配置中的行。
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
Rails 保證“讀你自己寫的”並將你的 GET 或 HEAD 請求傳送到
編寫器,如果它在 delay
視窗內。預設情況下,延遲設定為 2 秒。你
應該根據您的資料庫基礎結構更改此設定。 Rails 不保證“讀
最近寫入”延遲視窗內的其他使用者,並將傳送 GET 和 HEAD 請求
複製到副本,除非他們最近寫過。
Rails 中的自動連線切換比較原始,故意沒有 做很多。目標是一個演示如何進行自動連線的系統 切換足夠靈活,可以由應用程式開發人員自定義。
Rails 中的設定允許您輕鬆更改切換的完成方式和內容 它基於的引數。假設您想使用 cookie 而不是 session 來 決定何時交換連線。您可以編寫自己的類:
class MyCookieResolver
# code for your cookie class
end
然後將其傳遞給中介軟體:
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = MyCookieResolver
5 使用手動連線切換
在某些情況下,您可能希望應用程式連線到寫入器或副本 並且自動連線切換是不夠的。例如,您可能知道對於一個 您總是希望將請求傳送到副本的特定請求,即使您在 POST 請求路徑。
為此,Rails 提供了一個 connected_to
方法,該方法將切換到您的連線
需要。
ActiveRecord::Base.connected_to(role: :reading) do
# all code in this block will be connected to the reading role
end
connected_to
呼叫中的“角色”查詢在該呼叫上連線的連線
連線處理程式(或角色)。 reading
連線處理程式將儲存所有連線
透過 connects_to
與角色名稱 reading
連線。
請注意,帶有角色的 connected_to
將查詢現有連線並切換
使用連線規範名稱。這意味著如果你傳遞一個未知的角色
像 connected_to(role: :nonexistent)
你會得到一個錯誤,說
ActiveRecord::ConnectionNotEstablished (No connection pool for 'ActiveRecord::Base' found for the 'nonexistent' role.)
如果您希望 Rails 確保執行的任何查詢都是隻讀的,請傳遞 prevent_writes: true
。
這只是防止將看起來像寫入的查詢傳送到資料庫。
您還應該將副本資料庫配置為以只讀模式執行。
ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do
# Rails will check each query to ensure it's a read query
end
6 水平分片
水平分片是當您拆分資料庫以減少每個資料庫的行數時 資料庫伺服器,但在“分片”之間保持相同的模式。這通常稱為“多租戶” 分片。
Rails 中支援水平分片的 API 類似於多資料庫/垂直 自 Rails 6.0 以來就存在的分片 API。
分片在三層配置中宣告如下:
production:
primary:
database: my_primary_database
adapter: mysql2
primary_replica:
database: my_primary_database
adapter: mysql2
replica: true
primary_shard_one:
database: my_primary_shard_one
adapter: mysql2
primary_shard_one_replica:
database: my_primary_shard_one
adapter: mysql2
replica: true
Model 然後透過 shards
key 與 connects_to
API 連線:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
default: { writing: :primary, reading: :primary_replica },
shard_one: { writing: :primary_shard_one, reading: :primary_shard_one_replica }
}
end
然後 models 可以透過 connected_to
API 手動交換連線。如果
使用分片,必須同時傳遞 role
和 shard
:
ActiveRecord::Base.connected_to(role: :writing, shard: :default) do
@id = Person.create! # Creates a record in shard default
end
ActiveRecord::Base.connected_to(role: :writing, shard: :shard_one) do
Person.find(@id) # Can't find record, doesn't exist because it was created
# in the default shard
end
水平分片 API 也支援只讀副本。你可以交換
角色和帶有 connected_to
API 的分片。
ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one) do
Person.first # Lookup record from read replica of shard one
end
7 遷移到新的連線處理
在 Rails 6.1+ 中,Active Record 為連線管理提供了一個新的內部 API。
在大多數情況下,除了選擇加入
新行為(如果從 6.0 及以下升級)透過設定
config.active_record.legacy_connection_handling = false
。如果您只有一個數據庫
應用程式,不需要其他更改。如果您有多個數據庫應用程式
如果您的應用程式使用這些方法,則需要進行以下更改:
-
connection_handlers
和connection_handlers=
在新連線中不再起作用 處理。例如,如果您在其中一個連線處理程式上呼叫方法,connection_handlers[:reading].retrieve_connection_pool("ActiveRecord::Base")
您現在需要將該呼叫更新為connection_handlers.retrieve_connection_pool("ActiveRecord::Base", role: :reading)
。 - 對
ActiveRecord::Base.connection_handler.prevent_writes
的呼叫需要更新 到ActiveRecord::Base.connection.preventing_writes?
。 - 如果您需要所有池,包括寫入和讀取,則提供了一種新方法
處理程式。呼叫
connection_handler.all_connection_pools
來使用它。雖然在大多數情況下 您需要使用connection_handler.connection_pool_list(:writing)
或connection_handler.connection_pool_list(:reading)
。 - 如果您在應用程式中關閉
legacy_connection_handling
,任何不受支援的方法 將引發錯誤(即connection_handlers=
)。
8 粒度資料庫連線切換
在 Rails 6.1 中,可以為一個數據庫切換連線而不是
全球所有資料庫。要使用此功能,您必須首先設定
在您的應用程式中 config.active_record.legacy_connection_handling
到 false
配置。大多數應用程式不需要做任何其他
更改,因為公共 API 具有相同的行為。請參閱上述部分
如何啟用和遷移遠離 legacy_connection_handling
。
將 legacy_connection_handling
設定為 false
,任何抽象連線類
將能夠在不影響其他連線的情況下切換連線。這
用於切換 AnimalsRecord
查詢以從副本讀取
同時確保您的 ApplicationRecord
查詢轉到主要查詢。
AnimalsRecord.connected_to(role: :reading) do
Dog.first # Reads from animals_replica
Person.first # Reads from primary
end
也可以為分片粒度交換連線。
AnimalsRecord.connected_to(role: :reading, shard: :shard_one) do
Dog.first # Will read from shard_one_replica. If no connection exists for shard_one_replica,
# a ConnectionNotEstablished error will be raised
Person.first # Will read from primary writer
end
要僅切換主資料庫叢集,請使用 ApplicationRecord
:
ApplicationRecord.connected_to(role: :reading, shard: :shard_one) do
Person.first # Reads from primary_shard_one_replica
Dog.first # Reads from animals_primary
end
ActiveRecord::Base.connected_to
保持切換能力
全球連線。
8.1 透過跨資料庫連線處理 associations
從 Rails 7.0+ 開始,Active Record 有一個選項來處理 associations 將執行
跨多個數據庫的連線。如果您有一個透過 association 或有一個透過 association
如果您想禁用加入並執行 2 個或更多查詢,請傳遞 disable_joins: true
選項。
例如:
class Dog < AnimalsRecord
has_many :treats, through: :humans, disable_joins: true
has_many :humans
belongs_to :home
has_one :yard, through: :home, disable_joins: true
end
以前在沒有 disable_joins
的情況下呼叫 @dog.treats
或在沒有 disable_joins
的情況下呼叫 @dog.yard
會引發錯誤,因為資料庫無法處理跨叢集的連線。隨著
disable_joins
選項,Rails 會生成多個選擇查詢
以避免嘗試跨叢集加入。對於上面的 association,@dog.treats
會生成
以下 SQL:
SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ? [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?) [["human_id", 1], ["human_id", 2], ["human_id", 3]]
而 @dog.yard
會生成以下 SQL:
SELECT "home"."id" FROM "homes" WHERE "homes"."dog_id" = ? [["dog_id", 1]]
SELECT "yards".* FROM "yards" WHERE "yards"."home_id" = ? [["home_id", 1]]
使用此選項需要注意一些重要事項:
1) 可能會有效能影響,因為現在將執行兩個或多個查詢(取決於
在 association 上)而不是連線。如果 humans
的選擇返回大量 ID
treats
的選擇可能會發送過多的 ID。
2) 由於我們不再執行連線,具有順序或限制的查詢現在在記憶體中排序,因為
一張桌子上的訂單不能應用到另一張桌子上。
3) 必須將此設定新增到您要禁用加入的所有 associations。
Rails 無法為您猜測這一點,因為 association 載入是懶惰的,在 @dog.treats
中載入 treats
Rails 已經需要知道應該生成什麼 SQL。
9 注意事項
9.1 水平分片的自動交換
雖然 Rails 現在支援用於連線和交換分片連線的 API,但它確實支援
尚不支援自動交換策略。任何分片交換都需要手動完成
透過中介軟體或 around_action
在您的應用程式中。
9.2 負載均衡副本
Rails 也不支援副本的自動負載平衡。這是很 取決於您的基礎設施。我們可以實現基本的、原始的負載平衡 將來,但對於大規模應用程式,這應該是您的應用程式 在 Rails 之外處理。
9.3 架構快取
如果您使用架構快取和多個數據庫,則需要編寫一個初始化程式 從您的應用程式載入架構快取。這不是我們可以解決的問題 是 Rails 6.0 的時候了,但希望很快能在未來的版本中使用它。
回饋
我們鼓勵您幫助提高本指南的品質。
如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。
您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。
如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue。
關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區。