【问题标题】:Temporarily increase 'statement_timeout' for Postgres queries in Rails?暂时增加 Rails 中 Postgres 查询的“statement_timeout”?
【发布时间】:2018-10-23 19:31:16
【问题描述】:

我已经将database.yml 中的statement_timeout 配置为几秒钟,但我的应用程序中有一些昂贵的查询,这需要更长的查询执行时间。在每个查询级别上实现此目标的推荐方法是什么? 我需要临时将statement_timeout 设置为更大的值,执行查询并将其重置为默认值?或者甚至不需要重置?

【问题讨论】:

    标签: ruby-on-rails database postgresql timeout


    【解决方案1】:

    我认为您只能通过更改整个连接的statement_timeout 然后将其恢复回来来实现:

      def execute_expensive_query
        ActiveRecord::Base.connection.execute 'SET statement_timeout = 600000' # 10 minutes
        # DB query with long execution time
      ensure
        ActiveRecord::Base.connection.execute 'SET statement_timeout = 5000' # 5 seconds
      end
    

    在数据库级别,您可以为当前事务设置statement_timeout,仅按照this guide

    BEGIN;
    SET LOCAL statement_timeout = 250;
    ...
    COMMIT;
    

    【讨论】:

      【解决方案2】:

      为了扩展已接受的答案,以下是实现模块 DatabaseTimeout 的方法,该模块还确保将 statement_timeout 设置重置为其原始值。

      # Ruby's `Timeout` doesn't prevent queries from running for a long time.
      #
      # To prove this, run the following in a console (yes, twice):
      #   Timeout.timeout(1.second) { ActiveRecord::Base.connection.execute('SELECT pg_sleep(100);') }
      #   Timeout.timeout(1.second) { ActiveRecord::Base.connection.execute('SELECT pg_sleep(100);') }
      # => The 2nd call should run for a long time.
      #
      # DatabaseTimeout's purpose is to enforce that each query doesn't run for more than the given timeout:
      #   DatabaseTimeout.timeout(1.second) { ActiveRecord::Base.connection.execute('SELECT pg_sleep(100);') }
      #   DatabaseTimeout.timeout(1.second) { ActiveRecord::Base.connection.execute('SELECT pg_sleep(100);') }
      # => Both queries are interrupted after 1 second
      module DatabaseTimeout
        # Usage: DatabaseTimeout.timeout(10) { run_some_query }
        def self.timeout(nb_seconds)
          original_timeout = ActiveRecord::Base.connection.execute('SHOW statement_timeout').first['statement_timeout']
          ActiveRecord::Base.connection.execute("SET statement_timeout = '#{nb_seconds.to_i}s'")
          yield
        ensure
          if original_timeout
            ActiveRecord::Base.connection.execute("SET statement_timeout = #{original_timeout}")
          end
        end
      end
      

      【讨论】:

      • 在确保中的 original_timeout 周围需要单引号
      • 这是如何处理交易的?看来 SHOW statement_timeout 然后是 connection.execute ,然后是实际的事务查询会导致问题。还是我误解了 Rails 在事务中如何处理这个问题?
      • @berkes 在事务中调用DatabaseTimeout.timeout 时,SHOW 语句和 2 个 SET 语句是事务的一部分,我认为这不是问题。
      • @MatthieuLibeer 谢谢。但这意味着您必须将所有 DatabaseTimeout.timeout 包装在一个数据库事务中,或者冒着更改超时时间污染并发查询(在同一个 AR 连接上)的风险,不是吗?
      • 通常在 Rails 应用程序中,1 个进程 = 1 个连接,所以你很好,除非你正在用线程做一些巫术。如果要在事务级别设置statement_timeout,可以使用SET LOCAL statement_timeout = '10s';,需要包装在事务中:)
      猜你喜欢
      • 1970-01-01
      • 2012-07-26
      • 2018-04-13
      • 1970-01-01
      • 1970-01-01
      • 2022-08-04
      • 1970-01-01
      • 2019-01-08
      • 1970-01-01
      相关资源
      最近更新 更多