如何为单个迁移设置 statement_timeout?

6

我想为单个迁移设置PostgreSQL statement_timeout,但似乎无法实现。这是我的尝试:

def change
  execute <<~SQL
    SET LOCAL statement_timeout = 1; -- ms

    -- this does not cause a timeout which is expected, because pg
    -- only applies the timeout to the next protocol message / statement,
    -- and rails sends everthing inside execute in the same statement
    select pg_sleep(1); -- seconds
  SQL

  # if uncommented, this DOES cause a timeout, which is expected
  # execute <<~SQL
  #   select pg_sleep(1); -- seconds
  # SQL

  # this does not cause a timeout, which is unexpected
  remove_column :foos, :bar

  # we do get here, which is unexpected
  raise "we finished"
end

我该怎么做呢?


这里发生了其他的事情:BEGIN test(5432)=> SET LOCAL statement_timeout = 1; select pg_sleep(1); SET ERROR: canceling statement due to statement timeout当您运行代码时,Postgres日志显示了什么? - Adrian Klaver
@AdrianKlaver 当我关闭查询日志时(没有错误),日志中没有任何内容。您想查看查询日志吗? - John Bachir
我开始怀疑我的“问题”是删除列所需的时间少于1毫秒。我正在这里探索:https://dba.stackexchange.com/questions/269251/how-can-i-see-execution-time-of-an-alter-table-statement - John Bachir
1
@JohnBachir 在你编辑代码示例后(删除 disable_ddl_transaction!),现在它并没有什么意义。你对 pg_sleep 的第一个调用确实会引发超时。在同一次 execute 调用中发送 SETpg_sleep 没有任何区别。而且,很可能你的 remove_column 足够快,以至于 PG 在检查是否超过你设置的任何超时之前就已经完成了它。 - rafbm
3个回答

3
我假设你的问题是关于在迁移中为一个单独的语句设置 statement_timeout(而不是为一个单独的迁移设置)。您可以使用Postgres的SET而不是SET LOCAL来实现这一点。请保留HTML标签。
class SomeMigration < ActiveRecord::Migration[6.0]
  # Disables BEGIN/COMMIT around migration. SET LOCAL will have no effect.
  disable_ddl_transaction!

  def change
    # Applies to the current database session, until changed or reset.
    execute <<~SQL
      SET statement_timeout = '1s';
    SQL

    # Will raise `ActiveRecord::QueryCanceled`.
    execute <<~SQL
      SELECT pg_sleep(2);
    SQL

    # Resets (disables) timeout.
    execute <<~SQL
      SET statement_timeout = DEFAULT;
    SQL

    # Will not raise.
    execute <<~SQL
      SELECT pg_sleep(2);
    SQL
  end
end

请注意,如果您不调用disable_ddl_transaction!,则可以使用SET LOCAL。但是,两者的组合不起作用,因为事务块外的SET LOCAL没有任何效果。它会记录以下内容:WARNING:SET LOCAL只能在事务块中使用
PS:您对disable_ddl_transaction!的调用会引发NoMethodError。 它应该位于def change定义之前。

感谢@rafbm - 我尝试了这个,我认为它与您的建议兼容,但超时没有起作用 https://gist.github.com/jjb/349d2ea7fc685a0159e06fc9e62b3af1 - John Bachir
@JohnBachir 你有尝试使用普通的 pg_sleep 调用进行测试吗?如果你的简单的 remove_column 操作足够快,可能会在 PG 检查是否超过设置的任何超时时间之前就完成了。 - rafbm

0
你可以尝试使用更低级别的exec_query,甚至可以使用execute_and_clear或者更低级别的exec_no_cache

0

在打开log_duration并进行更多调查后,我认为我得出的结论是,即使记录了这样的日志,postgres也不认为删除列需要超过1毫秒的时间。

statement: ALTER TABLE "foos" DROP COLUMN "bar"
duration: 1.171 ms
duration: 0.068 ms
duration: 0.328 ms

但是使用以下方法:

  1. 我能够让另一种类型的查询由于语句超时而失败
  2. 我能够让删除列由于锁定超时而失败(同时在psql会话中保持锁定)
class TimeoutTest < ActiveRecord::Migration[5.2]

  def change
    execute <<~SQL
      SET statement_timeout = 1; -- ms
      SET lock_timeout = 1; -- ms
    SQL

    # I can't get this to fail due to a statement timeout, but does fail due to lock timeout
    # interestingly, if both timeouts are set, it will be statement timeout which happens first. not
    # sure if this is a pg bug, or if calculating the lock timeout somehow increases the overall statement
    # overhead
    remove_column :foos, :bar

    # With 100k of rows of test data, this does fail the statement timeout
    Foo.unscoped.update_all(bar: 5)
  end
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接