【问题标题】:How can I set statement_timeout for an individual migration?如何为单个迁移设置 statement_timeout?
【发布时间】:2020-09-21 11:28:51
【问题描述】:

我想为个人迁移设置 postgres 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

我该怎么做?

【问题讨论】:

  • 这里发生了其他事情:test(5432)=&gt; begin ; BEGIN test(5432)=&gt; SET LOCAL statement_timeout = 1; select pg_sleep(1); SET ERROR: canceling statement due to statement timeout 运行代码时 Postgres 日志显示什么?
  • @AdrianKlaver 当我关闭查询日志时,日志中没有任何内容(没有错误)。要查看查询日志吗?
  • 我开始怀疑我的“问题”是删除列所需的时间不到 1 毫秒。我在这里探索:dba.stackexchange.com/questions/269251/…
  • @JohnBachir 在您对代码示例进行编辑(删除disable_ddl_transaction!)之后,现在它真的没有意义了。您对pg_sleep 的第一次调用确实会引发超时。在同一个execute 调用中发送SETpg_sleep 没有区别。此外,很可能你的remove_column 足够快,以至于 PG 甚至在它有机会检查它是否超过你设置的任何超时之前就完成了它。

标签: ruby-on-rails postgresql database-migration


【解决方案1】:

在打开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

【讨论】:

    【解决方案2】:

    我假设您的问题是关于在您的迁移中为个人声明设置statement_timeout(而不是个人迁移)。

    您可以使用Postgres’s SET instead of SET LOCAL 实现此目的。

    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 can only be used in transaction blocks

    PS:您对disable_ddl_transaction! 的调用将引发NoMethodError。它应该在def change 定义之前。

    【讨论】:

    • 谢谢@rafbm - 我试过了,我认为这与你的建议兼容,超时不起作用gist.github.com/jjb/349d2ea7fc685a0159e06fc9e62b3af1
    • @JohnBachir 你用普通的pg_sleep 电话测试过吗?如果您的简单 remove_column 足够快以至于 PG 在有机会检查它是否超过您设置的任何超时之前完成它,我不会感到惊讶。
    【解决方案3】:

    您可以尝试使用较低级别的exec_query,或者使用execute_and_clear 使用更低级别,或者使用exec_no_cache 继续使用更低级别。

    【讨论】:

      猜你喜欢
      • 2021-12-03
      • 1970-01-01
      • 2020-03-28
      • 2022-08-04
      • 2019-07-29
      • 1970-01-01
      • 2014-07-28
      • 2018-11-23
      • 2021-09-27
      相关资源
      最近更新 更多