le mie informazioni di contatto
Posta[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
da active_record Analizzare nell'istruzione SQLSuccessivamente, invialo al database,Eseguire prima PREPARE Dichiarazioni preparate, istruzioni SQL verranno analizzate, analizzate, ottimizzate e riscritte.Quando il seguitoEmettere un comando EXECUTE, la dichiarazione preparata sarà pianificata ed eseguita.pg_prepared_statements
per facilitare la prossima volta che chiamerai affermazioni simili.eseguire direttamente dichiarazioni, invece di analizzare, analizzare e ottimizzare, evitando la duplicazione del lavoro e migliorando l'efficienza.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]]
Come nell'esempio seguente, quando si esegue SELECT dopo aver aggiunto o eliminato campi,banca dati pglanceràcached plan must not change result type
, su rotaieregistrazione_attivaOttieni questo errore e poi lanciaActiveRecord::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
erroreexec_cache
Metodo, ho scoperto che il metodo di gestione degli errori di rails per pg è: raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
prepare_statement
metodo nella cache delle istruzioni preparatemodule 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 e versioni successive possono disabilitare questa funzionalità impostando ready_statements nel database su false.
default: &default
adapter: postgresql
encoding: unicode
prepared_statements: false
Rails6 e versioni precedenti non sono state testate. Se quanto sopra non funziona, puoi provare a creare un nuovo file di inizializzazione.
# 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)
verificare:
User.all
ActiveRecord::Base.connection.execute('select * from pg_prepared_statements').values
==> []
Conclusione: nei piccoli progetti non importa se disabiliti questa funzione e le prestazioni rimarranno quasi inalterate. Tuttavia, nei progetti di grandi dimensioni, maggiore è il numero degli utenti e le istruzioni di query più complesse, maggiori saranno i vantaggi che questa funzione porterà. quindi puoi decidere se disabilitarlo in base alla situazione reale.
select *
diventareselect id, name
Tali campi specifici, in rails7Soluzione ufficialeQuesto è tutto# 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
Conclusione: il problema con questa soluzione è che l'aggiunta di campi può essere risolta perfettamente, ma l'eliminazione dei campi causerà comunque errori. Ad esempio, dopo aver eliminato il campo del nome, se il nome nell'istruzione preparata select id, il nome degli utenti non esiste. verrà segnalato un errore. Anche la soluzione ufficiale di Rails7 presenta questo problema
Conclusione: il riavvio dell'applicazione causerà un servizio temporaneo 502 Non disponibile Naturalmente, quando si distribuisce l'applicazione, il servizio deve essere riavviato e verrà visualizzato anche 502, quindi è meglio eseguire la distribuzione quando nessuno vi accede (nel mezzo di). la notte?), in modo che vengano visualizzati meno errori possibiliPreparedStatementCacheExpired
Segnala un errore
transaction
metodoclass 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
Conclusione: Nota importante: se invii e-mail, pubblichi su API o esegui altre operazioni che interagiscono con il mondo esterno come parte di una transazione, ciò potrebbe far sì che alcune di queste operazioni si ripetano occasionalmente due volte. Questo è il motivo per cui Rails ufficialmente non esegue i tentativi automaticamente, ma li lascia agli sviluppatori dell'applicazione.
>>>>>>>Quando provo personalmente questo metodo, ricevo ancora errori.
ActiveRecord::Base.connection.clear_cache!
Non ho trovato una soluzione perfetta