Mysql
 sql >> база данни >  >> RDS >> Mysql

Превключване между множество бази данни в Rails без прекъсване на транзакции

Това е труден проблем, поради тясното свързване вътре в ActiveRecord , но успях да създам някакво доказателство за концепция, която работи. Или поне изглежда, че работи.

Някои предистория

ActiveRecord използва ActiveRecord::ConnectionAdapters::ConnectionHandler клас, който е отговорен за съхраняване на пулове за връзки по модел. По подразбиране има само един пул за връзки за всички модели, тъй като обикновеното приложение Rails е свързано към една база данни.

След изпълнение на establish_connection за различна база данни в конкретен модел се създава нов пул за връзки за този модел. А също и за всички модели, които могат да наследят от него.

Преди да изпълните каквато и да е заявка, ActiveRecord първо извлича пул за връзки за съответния модел и след това извлича връзката от пула.

Обърнете внимание, че горното обяснение може да не е 100% точно, но трябва да е близко.

Решение

Така че идеята е да се замени манипулатора на връзка по подразбиране с персонализиран такъв, който ще връща пул за връзки въз основа на предоставеното описание на фрагмента.

Това може да се приложи по много различни начини. Направих го, като създадох прокси обекта, който предава имена на фрагменти като прикрит ActiveRecord класове. Манипулаторът на връзката очаква да получи AR модел и разглежда name свойство, а също и в superclass да вървят по йерархичната верига на модела. Внедрих DatabaseModel клас, който по същество е име на фрагмент, но се държи като AR модел.

Внедряване

Ето примерна реализация. Използвах sqlite база данни за простота, можете просто да стартирате този файл без никаква настройка. Можете също да разгледате този същност

# Define some required dependencies
require "bundler/inline"
gemfile(false) do
  source "https://rubygems.org"
  gem "activerecord", "~> 4.2.8"
  gem "sqlite3"
end

require "active_record"

class User < ActiveRecord::Base
end

DatabaseModel = Struct.new(:name) do
  def superclass
    ActiveRecord::Base
  end
end

# Setup database connections and create databases if not present
connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({
  "users_shard_1" => { adapter: "sqlite3", database: "users_shard_1.sqlite3" },
  "users_shard_2" => { adapter: "sqlite3", database: "users_shard_2.sqlite3" }
})

databases = %w{users_shard_1 users_shard_2}
databases.each do |database|
  filename = "#{database}.sqlite3"

  ActiveRecord::Base.establish_connection({
    adapter: "sqlite3",
    database: filename
  })

  spec = resolver.spec(database.to_sym)
  connection_handler.establish_connection(DatabaseModel.new(database), spec)

  next if File.exists?(filename)

  ActiveRecord::Schema.define(version: 1) do
    create_table :users do |t|
      t.string :name
      t.string :email
    end
  end
end

# Create custom connection handler
class ShardHandler
  def initialize(original_handler)
    @original_handler = original_handler
  end

  def use_database(name)
    @model= DatabaseModel.new(name)
  end

  def retrieve_connection_pool(klass)
    @original_handler.retrieve_connection_pool(@model)
  end

  def retrieve_connection(klass)
    pool = retrieve_connection_pool(klass)
    raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
    conn = pool.connection
    raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
    puts "Using database \"#{conn.instance_variable_get("@config")[:database]}\" (##{conn.object_id})"
    conn
  end
end

User.connection_handler = ShardHandler.new(connection_handler)

User.connection_handler.use_database("users_shard_1")
User.create(name: "John Doe", email: "[email protected]")
puts User.count

User.connection_handler.use_database("users_shard_2")
User.create(name: "Jane Doe", email: "[email protected]")
puts User.count

User.connection_handler.use_database("users_shard_1")
puts User.count

Мисля, че това трябва да даде идея как да се приложи готово за производство решение. Надявам се, че не съм пропуснал нищо очевидно тук. Мога да предложа няколко различни подхода:

  1. Подклас ActiveRecord::ConnectionAdapters::ConnectionHandler и презапишете тези методи, отговорни за извличането на пулове за връзки
  2. Създайте напълно нов клас, внедряващ същия API като ConnectionHandler
  3. Предполагам също така е възможно просто да се презапише retrieve_connection метод. Не помня къде е дефиниран, но мисля, че е в ActiveRecord::Core .

Мисля, че подходи 1 и 2 са правилният начин и трябва да обхващат всички случаи при работа с бази данни.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Mysql извлича всички редове с ограничение

  2. Промяна на стойността на полето за въвеждане, когато потребителят избере опция от полето за избор

  3. MySQL транзакция и тригер

  4. MySQL GROUP BY и попълване на празни редове

  5. Не може да се импортира MySQLdb - python - Windows 8.1