Rails 7中解除GET请求数据库readonly模式适配RailsMiniProfiler
RailsMiniProfiler在GET请求下无法写入数据库的解决方案
我清楚GET请求写入数据库不符合RESTful设计,但正在为基于Rails 7.0.3.1、MySQL 5.7(使用activerecord-pedantmysql2-adapter适配器)的项目部署RailsMiniProfiler。这个工具在开发环境中很实用,但除GET请求外其他请求都能正常运行——原因是该Gem尝试将分析信息写入rmp_profiled_request表,但Rails在GET请求下处于只读模式,报错如下:
E, [2024-05-29T14:32:10.392477 #58380] ERROR -- : [RailsMiniProfiler] Could not save profile: Write query attempted while in readonly mode: INSERT INTO `rmp_profiled_request`
问题
- 是否可以为
rmp_profiled_request表永久授予写入权限? - readonly模式的逻辑在哪里可以覆盖?
- 是否可以在
database.yml中配置特殊参数?
注:仅在开发环境使用RailsMiniProfiler,所有修改仅针对开发环境生效,目前查阅官方文档未找到相关解决方法。
技术细节
- Rails版本:7.0.3.1
- Ruby版本:ruby 3.1.4p223 (2023-03-30 revision 957bb7cb81) [x86_64-linux]
- 数据库:MySQL 5.7,使用pedant_mysql2适配器
config/database.yml
defaults: &defaults adapter: pedant_mysql2 username: <%= ENV['MY_API_DATABASE_USERNAME'] %> password: <%= ENV['MY_API_DATABASE_PASSWORD'] %> encoding: utf8 collation: utf8_unicode_ci pool: <%= ENV["DB_POOL"] %> timeout: 5000 automatic_close: false deployment_defaults: &deployment_defaults sslca: "/etc/ssl/certs/mysql-ssl-ca-cert.pem" init_command: "SET optimizer_switch='hash_join=off'" variables: group_concat_max_len: 15000 primary_defaults: &primary_defaults host: <%= ENV['MY_API_PRIMARY_DATABASE_HOST'] %> database: <%= ENV['MY_API_DATABASE_NAME'] %> primary_replica_defaults: &primary_replica_defaults host: <%= ENV['MY_API_PRIMARY_REPLICA_DATABASE_HOST'] %> database: <%= ENV['MY_API_DATABASE_NAME'] %> replica: true dw_defaults: &dw_defaults host: <%= ENV['MY_DW_DATABASE_HOST'] %> database: <%= ENV['MY_DW_DATABASE_NAME'] %> migrations_paths: db/migrate-dw dw_replica_defaults: &dw_replica_defaults host: <%= ENV['MY_DW_REPLICA_DATABASE_HOST'] %> database: <%= ENV['MY_DW_DATABASE_NAME'] %> replica: true development: primary: <<: *defaults <<: *primary_defaults primary_replica: <<: *defaults <<: *primary_replica_defaults dw: <<: *defaults <<: *dw_defaults dw_replica: <<: *defaults <<: *dw_replica_defaults test: primary: <<: *defaults host: <%= ENV['MY_API_PRIMARY_DATABASE_HOST'] %> database: my-api-test<%= ENV['TEST_ENV_NUMBER'] %> primary_replica: <<: *defaults host: <%= ENV['MY_API_PRIMARY_REPLICA_DATABASE_HOST'] %> database: my-api-test<%= ENV['TEST_ENV_NUMBER'] %> replica: true dw: <<: *defaults host: <%= ENV['MY_DW_DATABASE_HOST'] %> database: my-dw-test<%= ENV['TEST_ENV_NUMBER'] %> migrations_paths: db/dw_migrate dw_replica: <<: *defaults host: <%= ENV['MY_DW_REPLICA_DATABASE_HOST'] %> database: my-dw-test<%= ENV['TEST_ENV_NUMBER'] %> replica: true qa: primary: <<: *defaults <<: *deployment_defaults <<: *primary_defaults primary_replica: <<: *defaults <<: *deployment_defaults <<: *primary_replica_defaults dw: <<: *defaults <<: *deployment_defaults <<: *dw_defaults dw_replica: <<: *defaults <<: *deployment_defaults <<: *dw_replica_defaults staging: primary: <<: *defaults <<: *deployment_defaults <<: *primary_defaults primary_replica: <<: *defaults <<: *deployment_defaults <<: *primary_replica_defaults dw: <<: *defaults <<: *deployment_defaults <<: *dw_defaults dw_replica: <<: *defaults <<: *deployment_defaults <<: *dw_replica_defaults integration: primary: <<: *defaults <<: *deployment_defaults <<: *primary_defaults primary_replica: <<: *defaults <<: *deployment_defaults <<: *primary_replica_defaults dw: <<: *defaults <<: *deployment_defaults <<: *dw_defaults dw_replica: <<: *defaults <<: *deployment_defaults <<: *dw_replica_defaults production: primary: <<: *defaults <<: *deployment_defaults <<: *primary_defaults primary_replica: <<: *defaults <<: *deployment_defaults <<: *primary_replica_defaults dw: <<: *defaults <<: *deployment_defaults <<: *dw_defaults dw_replica: <<: *defaults <<: *deployment_defaults <<: *dw_replica_defaults ci: primary: <<: *defaults <<: *primary_defaults primary_replica: <<: *defaults <<: *primary_replica_defaults dw: <<: *defaults <<: *dw_defaults dw_replica: <<: *defaults <<: *dw_replica_defaults demo: primary: <<: *defaults adapter: mysql2 host: <%= ENV['MY_API_PRIMARY_DATABASE_HOST'] %> database: my-api-demo primary_replica: <<: *defaults adapter: mysql2 host: <%= ENV['MY_API_PRIMARY_REPLICA_DATABASE_HOST'] %> database: my-api-demo replica: true dw: <<: *defaults adapter: mysql2 host: <%= ENV['MY_DW_DATABASE_HOST'] %> database: my-dw-demo migrations_paths: db/migrate-dw dw_replica: <<: *defaults adapter: mysql2 host: <%= ENV['MY_DW_REPLICA_DATABASE_HOST'] %> database: my-dw-demo replica: true
解决方案
1. 关于为rmp_profiled_request表永久授予写入权限
这不是表权限问题——你的开发环境配置了主从分离,GET请求默认路由到只读副本,而副本库本身是只读的,所以不管表权限如何,都无法写入。核心是要让RailsMiniProfiler的写入操作强制走主库。
2. 覆盖readonly模式逻辑的方法
最直接的方式是修改RailsMiniProfiler的模型,让它始终使用主库连接。在开发环境下创建一个初始化文件config/initializers/rails_mini_profiler.rb:
if Rails.env.development? RailsMiniProfiler::ProfiledRequest.class_eval do def save(*) ActiveRecord::Base.connected_to(role: :writing) do super end end def save!(*) ActiveRecord::Base.connected_to(role: :writing) do super end end end end
这样每次保存分析数据时,都会强制切换到主库(可写连接),绕过GET请求的只读限制。
3. database.yml中的配置调整
如果开发环境不需要主从分离,直接简化配置,去掉replica节点:
development: primary: <<: *defaults <<: *primary_defaults dw: <<: *defaults <<: *dw_defaults
这样所有请求都会走主库,自然可以写入。但如果需要保留主从配置,优先使用上面的模型修改方法。
内容的提问来源于stack exchange,提问作者Oscar Ortiz García
相关产品推荐
相关产品推荐

