Mi información de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
it 'not raise ActiveRecord::PreparedStatementCacheExpired' do
create(:user)
User.first
User.find_by_sql('ALTER TABLE users ADD new_metric_column integer;')
ActiveRecord::Base.transaction { User.first }
end
User.all
por active_record Analizar en declaración SQLDespués de eso, envíelo a la base de datos,Ejecute PREPARE primero Declaraciones preparadas, las declaraciones SQL se analizarán, optimizarán y reescribirán.Cuando el seguimientoEmitir un comando EJECUTAR, la declaración preparada será planificada y ejecutada.pg_prepared_statements
para facilitar la próxima vez que llame a declaraciones similares.ejecutar directamente declaraciones, en lugar de analizar, analizar y optimizar, evitando la duplicación de trabajo y mejorando la eficiencia.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
$1"=>"a7", ""$user", public-SELECT "users".* FROM "users" /* loading for inspect */ LIMIT $1"=>"a8"}},
@statement_limit=1000, @connection=#<PG::Connection:0x00000001086b31a0>, @counter=8>
# 这个也可以看到,会在数据库中去查询
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.891+00:00", "{bigint}", false], ["a8", "SELECT "users".* FROM "users" /* loading for inspect
*/ LIMIT $1", "2024-07-11T07:04:47.772+00:00", "{bigint}", false]]
Como en el siguiente ejemplo, al ejecutar SELECT después de agregar o eliminar campos,página de base de datostirarácached plan must not change result type
, en rielesregistro activoRecibe este error y luego lanzaActiveRecord::PreparedStatementCacheExpired
ALTER TABLE users ADD COLUMN new_column integer;
ALTER TABLE users DROP COLUMN old_column;
添加或删除列,然后执行 SELECT *
删除 old_column 列然后执行 SELECT users.old_column
cached plan must not change result type
errorexec_cache
Método, descubrí que el método de manejo de errores de Rails para pg es: raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
prepare_statement
método en el caché de declaraciones preparadasmodule ActiveRecord
module ConnectionHandling
def exec_cache(sql, name, binds)
materialize_transactions
mark_transaction_written_if_write(sql)
update_typemap_for_default_timezone
stmt_key = prepare_statement(sql, binds)
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds, stmt_key) do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@connection.exec_prepared(stmt_key, type_casted_binds)
end
end
rescue ActiveRecord::StatementInvalid => e
raise unless is_cached_plan_failure?(e)
# Nothing we can do if we are in a transaction because all commands
# will raise InFailedSQLTransaction
if in_transaction?
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
else
@lock.synchronize do
# outside of transactions we can simply flush this query and retry
@statements.delete sql_key(sql)
end
retry
end
end
end
end
Rails6 y superiores pueden deshabilitar esta función estableciendo declaraciones_preparadas en la base de datos en falso.
default: &default
adapter: postgresql
encoding: unicode
prepared_statements: false
Rails6 y versiones inferiores no se han probado. Si lo anterior no funciona, puede intentar crear un nuevo archivo de inicialización.
# 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)
verificar:
User.all
ActiveRecord::Base.connection.execute('select * from pg_prepared_statements').values
==> []
Conclusión: en proyectos pequeños, no importa si deshabilita esta función y el rendimiento casi no se verá afectado. Sin embargo, en proyectos grandes, cuantos más usuarios y declaraciones de consulta más complejas, mayores serán los beneficios que traerá esta función. para que pueda decidir si desea desactivarlo según la situación real.
select *
convertirseselect id, name
Campos tan específicos, en Rails7Solución oficialEso es todo# config/application.rb
module MyApp
class Application < Rails::Application
config.active_record.enumerate_columns_in_select_statements = true
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
#__fake_column__是自定义的,不要是某个表中的字段就行,如果是[:id],那么 User.all就会被解析为select name from users,没有id了
self.ignored_columns = [:__fake_column__]
end
Conclusión: El problema con esta solución es que agregar campos se puede resolver perfectamente, pero eliminar campos aún causará errores. Por ejemplo, después de eliminar el campo de nombre, si el nombre en la declaración preparada selecciona id, el nombre de los usuarios no existe. Se informará un error. La solución oficial de Rails7 también tiene este problema.
Conclusión: reiniciar la aplicación provocará un servicio temporal 502 no disponible. Por supuesto, al implementar la aplicación, también debe reiniciar el servicio y también aparecerá 502, por lo que es mejor implementarlo cuando nadie acceda a él (en el). media noche?), para que haya la menor cantidad de errores posible.PreparedStatementCacheExpired
Informar un error
transaction
métodoclass ApplicationRecord < ActiveRecord::Base
class << self
def transaction(*args, &block)
retried ||= false
super
rescue ActiveRecord::PreparedStatementCacheExpired
if retried
raise
else
retried = true
retry
end
end
end
end
ApplicationRecord.transaction do ... end
oMyModel.transaction
Conclusión: Nota importante: si envía correos electrónicos, publica en API o realiza otras operaciones que interactúan con el mundo exterior como parte de una transacción, esto puede causar que algunas de estas operaciones ocurran ocasionalmente dos veces. Es por eso que Rails oficialmente no realiza reintentos automáticamente, sino que lo deja en manos de los desarrolladores de aplicaciones.
>>>>>>>Cuando pruebo este método yo mismo, todavía recibo errores.
ActiveRecord::Base.connection.clear_cache!
No encontré una solución perfecta.