моя контактная информация
Почтамезофия@protonmail.com
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
от active_record Разобрать оператор sqlПосле этого отправьте его в базу данных,Сначала выполните ПОДГОТОВКУ Подготовленные операторы, операторы SQL будут разобраны, проанализированы, оптимизированы и переписаны.Когда будет последующее наблюдениеВыдайте команду EXECUTE, подготовленное заявление будет запланировано и выполнено.pg_prepared_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
$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]]
Как и в следующем примере, при выполнении SELECT после добавления или удаления полей:база данных pgброситcached plan must not change result type
, в рельсахактивная_записьПолучите эту ошибку, а затем выбросьтеActiveRecord::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
ошибкаexec_cache
Метод, я обнаружил, что метод обработки ошибок рельсов для pg: raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
prepare_statement
метод в подготовленный кеш операторовmodule 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 и выше могут отключить эту функцию, установив для подготовленных_выражений в базе данных значение false.
default: &default
adapter: postgresql
encoding: unicode
prepared_statements: false
Rails6 и ниже не тестировались. Если описанное выше не работает, вы можете попробовать создать новый файл инициализации.
# 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
==> []
Вывод: в небольших проектах не имеет значения, отключите ли вы эту функцию, и производительность практически не пострадает. Однако в больших проектах, чем больше пользователей и более сложные операторы запросов, тем больше преимуществ принесет эта функция. поэтому вы можете решить, отключать ли его в зависимости от реальной ситуации.
select *
становитьсяselect id, name
Такие специфические поля в рельсах7Официальное решениеВот и все# 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
Вывод: проблема с этим решением заключается в том, что добавление полей может быть прекрасно решено, но удаление полей все равно будет вызывать ошибки. Например, после удаления поля имени, если имя в подготовленном операторе выбирает id, имя из пользователей не существует. будет сообщено об ошибке. Официальное решение Rails7 также имеет эту проблему.
Вывод: Перезапуск приложения вызовет временную ошибку 502 Unavailable. Разумеется, при развертывании приложения также потребуется перезапустить службу, и 502 тоже появится, поэтому лучше всего развертывать, когда к ней никто не обращается (в режиме). посреди ночи?), чтобы ошибок было как можно меньше.PreparedStatementCacheExpired
Сообщить об ошибке
transaction
методclass 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
илиMyModel.transaction
Вывод: Важное примечание. Если вы отправляете электронные письма, публикуете сообщения в API или выполняете другие операции, которые взаимодействуют с внешним миром в рамках транзакции, это может привести к тому, что некоторые из этих операций иногда могут выполняться дважды. Вот почему Rails официально не выполняет повторные попытки автоматически, а оставляет это на усмотрение разработчиков приложений.
>>>>>>>Когда я сам тестирую этот метод, у меня все равно возникают ошибки.
ActiveRecord::Base.connection.clear_cache!
Не нашел идеального решения