Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Chuyển đổi giữa nhiều cơ sở dữ liệu trong Rails mà không làm gián đoạn các giao dịch

Đây là một vấn đề phức tạp, do khớp nối chặt chẽ bên trong ActiveRecord , nhưng tôi đã cố gắng tạo ra một số bằng chứng về khái niệm hoạt động. Hoặc ít nhất có vẻ như nó hoạt động.

Một số thông tin cơ bản

ActiveRecord sử dụng ActiveRecord::ConnectionAdapters::ConnectionHandler lớp chịu trách nhiệm lưu trữ các nhóm kết nối cho mỗi mô hình. Theo mặc định, chỉ có một nhóm kết nối cho tất cả các mô hình, vì ứng dụng Rails thông thường được kết nối với một cơ sở dữ liệu.

Sau khi thực hiện establish_connection cho các cơ sở dữ liệu khác nhau trong mô hình cụ thể, nhóm kết nối mới được tạo cho mô hình đó. Và cũng cho tất cả các mô hình có thể kế thừa từ nó.

Trước khi thực hiện bất kỳ truy vấn nào, hãy ActiveRecord trước tiên truy xuất nhóm kết nối cho mô hình có liên quan và sau đó truy xuất kết nối từ nhóm.

Lưu ý rằng giải thích trên có thể không chính xác 100%, nhưng nó phải gần gũi.

Giải pháp

Vì vậy, ý tưởng là thay thế trình xử lý kết nối mặc định bằng trình xử lý tùy chỉnh sẽ trả về nhóm kết nối dựa trên mô tả phân đoạn được cung cấp.

Điều này có thể được thực hiện theo nhiều cách khác nhau. Tôi đã làm điều đó bằng cách tạo đối tượng proxy đang chuyển các tên phân đoạn dưới dạng ActiveRecord được ngụy trang các lớp học. Trình xử lý kết nối đang mong đợi nhận được mô hình AR và xem xét name thuộc tính và cả ở superclass để xem chuỗi mô hình phân cấp. Tôi đã triển khai DatabaseModel lớp về cơ bản là tên phân đoạn, nhưng nó hoạt động giống như mô hình AR.

Triển khai

Đây là ví dụ thực hiện. Tôi đã sử dụng cơ sở dữ liệu sqlite vì đơn giản, bạn chỉ có thể chạy tệp này mà không cần bất kỳ thiết lập nào. Bạn cũng có thể xem qua ý chính này

# 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

Tôi nghĩ điều này sẽ đưa ra ý tưởng về cách triển khai giải pháp sẵn sàng sản xuất. Tôi hy vọng tôi đã không bỏ lỡ bất cứ điều gì rõ ràng ở đây. Tôi có thể đề xuất một số cách tiếp cận khác nhau:

  1. Lớp con ActiveRecord::ConnectionAdapters::ConnectionHandler và ghi đè những phương thức chịu trách nhiệm truy xuất nhóm kết nối
  2. Tạo lớp hoàn toàn mới triển khai cùng một api với ConnectionHandler
  3. Tôi đoán cũng có thể chỉ cần ghi đè retrieve_connection phương pháp. Tôi không nhớ nó được định nghĩa ở đâu, nhưng tôi nghĩ nó nằm trong ActiveRecord::Core .

Tôi nghĩ các phương pháp tiếp cận 1 và 2 là cách để đi và nên áp dụng cho tất cả các trường hợp khi làm việc với cơ sở dữ liệu.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tính toán phần trăm từ lần truy cập gần đây trong MySQL

  2. Nhóm theo ngày từ dấu thời gian

  3. JPA nhiều liên quan đến nhiều quan hệ không chèn vào bảng đã tạo

  4. Hiệu suất MySQL của VIEW cho các bảng kết hợp với UNION ALL

  5. PHP - Có thực hành tốt để lưu các truy vấn MYSQL vào một tệp txt không?