848给我做一下88网站,网站建设万首先金手指13,网站由什么组成,设计学校网站模板免费下载一、问题#xff1a;
有时在 Postgres 上部署 Rails 应用程序时#xff0c;可能会看到 ActiveRecord::PreparedStatementCacheExpired 错误。仅当在部署中运行迁移时才会发生这种情况。发生这种情况是因为 Rails 利用 Postgres 的缓存准备语句(PreparedStatementCache)功能来…一、问题
有时在 Postgres 上部署 Rails 应用程序时可能会看到 ActiveRecord::PreparedStatementCacheExpired 错误。仅当在部署中运行迁移时才会发生这种情况。发生这种情况是因为 Rails 利用 Postgres 的缓存准备语句(PreparedStatementCache)功能来提高性能。这个功能在rails中默认是开启的。
二、问题复现
我们可以用rspec来复现这个错误 it not raise ActiveRecord::PreparedStatementCacheExpired docreate(:user)User.firstUser.find_by_sql(ALTER TABLE users ADD new_metric_column integer;)ActiveRecord::Base.transaction { User.first }end三、产生的原理
rails查询语句如User.all被 active_record 解析成sql语句后发送给数据库先执行PREPARE预备语句sql语句会被解析、分析、优化并且重写。当后续发出一个EXECUTE命令时该预备语句会被规划并且执行。rails会把查询语句存到pg_prepared_statements中以方便下次调用同类语句时候直接execute statements中的语句而不用再进行解析、分析、优化避免重复工作提高效率。
User.first
User.all
# 执行上面的2个查询后用connection.instance_variable_get(:statements)就可以看到缓存的准备语句
ActiveRecord::Base.connection.instance_variable_get(:statements)ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::StatementPool:0x00000001086b13c8
cache{78368{\$user\, public-SELECT \users\.* FROM \users\ ORDER BY \users\.\id\ ASC LIMIT
$1a7, \$user\, public-SELECT \users\.* FROM \users\ /* loading for inspect */ LIMIT $1a8}},
statement_limit1000, connection#PG::Connection:0x00000001086b31a0, counter8# 这个也可以看到会在数据库中去查询
ActiveRecord::Base.connection.execute(select * from pg_prepared_statements).values
(0.5ms) select * from pg_prepared_statements[[a7, SELECT \users\.* FROM \users\ ORDER BY \users\.\id\ ASC LIMIT $1, 2024-07-
11T07:03:06.89100:00, {bigint}, false], [a8, SELECT \users\.* FROM \users\ /* loading for inspect
*/ LIMIT $1, 2024-07-11T07:04:47.77200:00, {bigint}, false]]在 Postgres 中如果表的模式(schema)更改影响返回结果则预准备语句缓存将失效。具体说就是给表增加、删除字段或者修改字段的类型、长度等ddl操作。
如下面的例子添加或删除字段后执行SELECT时pg数据库就会抛出cached plan must not change result typerails中active_record获取到这个错误然后会抛出ActiveRecord::PreparedStatementCacheExpired
ALTER TABLE users ADD COLUMN new_column integer;
ALTER TABLE users DROP COLUMN old_column;
添加或删除列然后执行 SELECT *
删除 old_column 列然后执行 SELECT users.old_column部署服务中运行增、减、修改字段的迁移时用户发出的查询语句会从预准备语句缓存中直接拿sql直接进行excute但这时候因为表结构变化了预准备语句缓存就失效了pg数据库就会抛出cached plan must not change result type错误查看active_record源码中的exec_cache方法发现rails对pg的这个错误处理方式是 事务transaction中会直接抛出 raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)事务外的会把缓存statements中的这句删除并 try重试后会重新解析、分析、优化sql语句并执行prepare_statement方法放入预准备语句缓存中
module ActiveRecordmodule ConnectionHandlingdef exec_cache(sql, name, binds)materialize_transactionsmark_transaction_written_if_write(sql)update_typemap_for_default_timezonestmt_key prepare_statement(sql, binds)type_casted_binds type_casted_binds(binds)log(sql, name, binds, type_casted_binds, stmt_key) doActiveSupport::Dependencies.interlock.permit_concurrent_loads doconnection.exec_prepared(stmt_key, type_casted_binds)endendrescue ActiveRecord::StatementInvalid eraise unless is_cached_plan_failure?(e)# Nothing we can do if we are in a transaction because all commands# will raise InFailedSQLTransactionif in_transaction?raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)elselock.synchronize do# outside of transactions we can simply flush this query and retrystatements.delete sql_key(sql)endretryendendend
end所以出现在事务transaction中的这个错误就会导致事务回滚对业务来说就是请求失败了需要我们自己来处理
四、解决方法
1. 禁用缓存准备语句功能不推荐
rails6 以上可以把 database中 prepared_statements 设为 false来禁用这个功能
default: defaultadapter: postgresqlencoding: unicodeprepared_statements: falserails6以下没测试如果上面的不行可以试试新建个初始化文件
# config/initializers/disable_prepared_statements.rb:
db_configuration ActiveRecord::Base.configurations[Rails.env]
db_configuration.merge!(prepared_statements false)
ActiveRecord::Base.establish_connection(db_configuration)验证
User.all
ActiveRecord::Base.connection.execute(select * from pg_prepared_statements).values[]结论小型项目中其实禁用这个功能无所谓性能几乎不影响但是大型项目中用户越多越复杂的查询语句这个功能带来的受益越大所以可以根据实际情况来决定是否禁用
2. 使select * 变为 select id, name这样的具体字段 rails7中的官方解决方案就是这样的但只能解决新增字段引起的报错
rails7中 enumerate_columns_in_select_statements 设为 true
# config/application.rb
module MyAppclass Application Rails::Applicationconfig.active_record.enumerate_columns_in_select_statements trueend
endrails7以下没有这个配置可以用 ignored_columns来实现
class ApplicationRecord ActiveRecord::Baseself.abstract_class true#__fake_column__是自定义的不要是某个表中的字段就行如果是[:id],那么 User.all就会被解析为select name from users,没有id了self.ignored_columns [:__fake_column__]
end结论这个方案存在的问题是增加字段可以完美解决但是删除字段还会出现报错比如删除name字段后预准备语句select id, name from users中的name不存在了就会报错。 删除字段可以在 User.rb 中增加 self.ignored_columns [:name], 然后先重启服务再进行部署部署时候最好把 self.ignored_columns [:name] 删掉避免以后再加回 name 字段后select 不到rails7 官方的方案也存在这个问题所以这个方案感觉很麻烦
3. 重启rails应用
预准备语句缓存的生命周期只存在于一个数据库会话中关闭数据库连接重启应用会关闭原连接重新建立新连接那原来的预准备语句缓存就会清空重启后的sql请求就会重新缓存预准备语句就能正常拿到数据。
结论重启应用会出现短暂服务502不可用当然部署应用时候也是要重启服务的也会出现502所以最好是没人访问的时候半夜进行部署这样就会尽可能少的出现PreparedStatementCacheExpired报错
4. 重写 transaction 方法
class ApplicationRecord ActiveRecord::Baseclass selfdef transaction(*args, block)retried || falsesuperrescue ActiveRecord::PreparedStatementCacheExpiredif retriedraiseelseretried trueretryendendend
end重写后代码里写事务的地方改为使用 ApplicationRecord.transaction do ... end 或者 MyModel.transaction或者obj.transaction, 只要不用ActiveRecord::Base.transaction就行
结论重要提示如果在事务中有发送电子邮件、post到 API 或执行其他与外界交互的操作这可能会导致其中一些操作偶尔发生两次。这就是为什么 Rails官方不会自动执行重试而是将其留给应用程序开发人员。
我本人测试这个方法还是会继续报错
5. 手动清除预准备语句缓存 ActiveRecord::Base.connection.clear_cache!五、最终答案
没有找到一个完美的解决方案 文章转载自: http://www.morning.pdmsj.cn.gov.cn.pdmsj.cn http://www.morning.lpgw.cn.gov.cn.lpgw.cn http://www.morning.jgnst.cn.gov.cn.jgnst.cn http://www.morning.xdxpq.cn.gov.cn.xdxpq.cn http://www.morning.tbwsl.cn.gov.cn.tbwsl.cn http://www.morning.qxgmp.cn.gov.cn.qxgmp.cn http://www.morning.duqianw.com.gov.cn.duqianw.com http://www.morning.dtnyl.cn.gov.cn.dtnyl.cn http://www.morning.wfpmt.cn.gov.cn.wfpmt.cn http://www.morning.lwrks.cn.gov.cn.lwrks.cn http://www.morning.sfhjx.cn.gov.cn.sfhjx.cn http://www.morning.ydrn.cn.gov.cn.ydrn.cn http://www.morning.lqgtx.cn.gov.cn.lqgtx.cn http://www.morning.ntqnt.cn.gov.cn.ntqnt.cn http://www.morning.nkkr.cn.gov.cn.nkkr.cn http://www.morning.mynbc.cn.gov.cn.mynbc.cn http://www.morning.mzgq.cn.gov.cn.mzgq.cn http://www.morning.rnmdp.cn.gov.cn.rnmdp.cn http://www.morning.ytmx.cn.gov.cn.ytmx.cn http://www.morning.dhyqg.cn.gov.cn.dhyqg.cn http://www.morning.cpctr.cn.gov.cn.cpctr.cn http://www.morning.jqcrf.cn.gov.cn.jqcrf.cn http://www.morning.qqzdr.cn.gov.cn.qqzdr.cn http://www.morning.qcsbs.cn.gov.cn.qcsbs.cn http://www.morning.rfljb.cn.gov.cn.rfljb.cn http://www.morning.lsgjf.cn.gov.cn.lsgjf.cn http://www.morning.qtxwb.cn.gov.cn.qtxwb.cn http://www.morning.khyqt.cn.gov.cn.khyqt.cn http://www.morning.tftw.cn.gov.cn.tftw.cn http://www.morning.ysrtj.cn.gov.cn.ysrtj.cn http://www.morning.ffgbq.cn.gov.cn.ffgbq.cn http://www.morning.kcrw.cn.gov.cn.kcrw.cn http://www.morning.xbbrh.cn.gov.cn.xbbrh.cn http://www.morning.qhvah.cn.gov.cn.qhvah.cn http://www.morning.jfmjq.cn.gov.cn.jfmjq.cn http://www.morning.fyxr.cn.gov.cn.fyxr.cn http://www.morning.kynf.cn.gov.cn.kynf.cn http://www.morning.lbpqk.cn.gov.cn.lbpqk.cn http://www.morning.rpgdd.cn.gov.cn.rpgdd.cn http://www.morning.kxbdm.cn.gov.cn.kxbdm.cn http://www.morning.bbjw.cn.gov.cn.bbjw.cn http://www.morning.ylxgw.cn.gov.cn.ylxgw.cn http://www.morning.mnrqq.cn.gov.cn.mnrqq.cn http://www.morning.yxlhz.cn.gov.cn.yxlhz.cn http://www.morning.rdwm.cn.gov.cn.rdwm.cn http://www.morning.rqhdt.cn.gov.cn.rqhdt.cn http://www.morning.jhrqn.cn.gov.cn.jhrqn.cn http://www.morning.wmmjw.cn.gov.cn.wmmjw.cn http://www.morning.jyfrz.cn.gov.cn.jyfrz.cn http://www.morning.mrlkr.cn.gov.cn.mrlkr.cn http://www.morning.paxkhqq.cn.gov.cn.paxkhqq.cn http://www.morning.rhdln.cn.gov.cn.rhdln.cn http://www.morning.mnnxt.cn.gov.cn.mnnxt.cn http://www.morning.cyyhy.cn.gov.cn.cyyhy.cn http://www.morning.tscsd.cn.gov.cn.tscsd.cn http://www.morning.rwtlj.cn.gov.cn.rwtlj.cn http://www.morning.nqrfd.cn.gov.cn.nqrfd.cn http://www.morning.ryjl.cn.gov.cn.ryjl.cn http://www.morning.hdwjb.cn.gov.cn.hdwjb.cn http://www.morning.qfths.cn.gov.cn.qfths.cn http://www.morning.zbhfs.cn.gov.cn.zbhfs.cn http://www.morning.rxzcl.cn.gov.cn.rxzcl.cn http://www.morning.fkyqm.cn.gov.cn.fkyqm.cn http://www.morning.jbztm.cn.gov.cn.jbztm.cn http://www.morning.mwnch.cn.gov.cn.mwnch.cn http://www.morning.wxrbl.cn.gov.cn.wxrbl.cn http://www.morning.cpqqf.cn.gov.cn.cpqqf.cn http://www.morning.cfccp.cn.gov.cn.cfccp.cn http://www.morning.bnqcm.cn.gov.cn.bnqcm.cn http://www.morning.ztcxx.com.gov.cn.ztcxx.com http://www.morning.jbpodhb.cn.gov.cn.jbpodhb.cn http://www.morning.crrjg.cn.gov.cn.crrjg.cn http://www.morning.rhnn.cn.gov.cn.rhnn.cn http://www.morning.eronghe.com.gov.cn.eronghe.com http://www.morning.nnwnl.cn.gov.cn.nnwnl.cn http://www.morning.yqyhr.cn.gov.cn.yqyhr.cn http://www.morning.hnk25076he.cn.gov.cn.hnk25076he.cn http://www.morning.daidudu.com.gov.cn.daidudu.com http://www.morning.xtxp.cn.gov.cn.xtxp.cn http://www.morning.dfqmy.cn.gov.cn.dfqmy.cn