【问题标题】:How to track query progress in PostgreSQL?如何在 PostgreSQL 中跟踪查询进度?
【发布时间】:2020-10-16 14:33:06
【问题描述】:

有没有可以跟踪PostgreSQL中长查询进度的插件或脚本?

我的意思是我需要在 Java 中设置与 Postgres 中的某些更新查询相关的进度条值。我在互联网上搜索,但我刚刚发现了一些在任何 RDBMS 系统中都没有正式实现的论文。

【问题讨论】:

  • 为查询生成可靠、准确的进度指示是一个非常困难的问题。当您连续多次执行相同的查询时,查询的执行时间甚至不一样。
  • 一般来说不是,不是。如果您显示使用查询和执行计划,我们可能能够推荐一些东西。
  • dba.se 上建议了一些技术,请参阅:How do I find out how far along my PostgreSQL query is?

标签: postgresql


【解决方案1】:

我在这里找到了一个很好的答案:Tracking progress of an update statement

诀窍是首先创建一个序列(随意命名):

CREATE SEQUENCE query_progress START 1;

然后附加到查询的 WHERE 部分:

AND NEXTVAL('query_progress')!=0

现在可以查询进度了:

SELECT NEXTVAL('query_progress');

最后别忘了去掉序列:

DROP SEQUENCE query_progress;

请注意,这很可能会使您的查询运行得更慢,并且每次您检查进度时,它都会额外增加该值。上面的链接建议创建一个临时序列,但 PostgreSQL 似乎没有让它们在会话中可见。

【讨论】:

  • 这很酷,很有创意。它也很 hacky,但是哦……希望 Postgres 有一天能给我们一个真正的解决方案。
  • 这是一个有趣的解决方案,但我认为查询进度最好调用currval函数:SELECT CURRVAL('query_progress'); PS:更新后可以调用currval函数查询完成,否则会报错:ERROR: currval of sequence "query_progress" is not yet defined in this session;在这种情况下,您可以使用此查询:SELECT last_value FROM query_progress;,即使您的更新查询没有开始,它也应该返回一个值。
【解决方案2】:

我想出了一个可能有帮助的方法。但如果您想将其实现到 Java 等代码中,则可能需要进一步处理。

方法是检查页面内容以跟踪进度。

Postgresql 有一个名为 pageinspect 的扩展,可以检查特定表的页面信息。

这里有详细信息: https://www.postgresql.org/docs/current/pageinspect.html

这里也花点时间了解一下postgresql的页面布局

https://www.postgresql.org/docs/current/storage-page-layout.html

特别看xmin、xmax和ctid

我假设行插入遵循特定顺序的表格。就像桌子的 pkey。而且任何长时间的更新都可能会附加新的页面。

我还假设主键 ID 大部分是连续的,几乎没有间隔。由于这只是一个估计,我认为这种情况是可以的。

无法通过 SELECT relname, relpages FROM pg_class 找到总页码,因为它没有更新。

如果页面索引在stage中不存在,你会遇到异常(但你会找到页面,即使它没有在pg_class左右更新),所以在“page_index”上做一点“二分查找” " 查找您拥有的最大页面。不需要很准确。

使用

SELECT backend_xid FROM pg_stat_activity WHERE pid = process-id

查找您当前的交易 ID。

使用

SELECT lp,t_xmin,t_xmax,t_ctid,t_bits,t_data FROM heap_page_items(get_raw_page('relation_name', page_index));

在我正在处理的示例中,它可能看起来像这样

SELECT lp,t_xmin,t_xmax,t_ctid,t_bits,t_data FROM heap_page_items(get_raw_page('foo', 3407000));

lp | t_xmin | t_xmax | t_ctid | t_bits | t_data

1 | 592744 | 592744 | (3407000,1) | 110000000111000000000000 | \xd1100000000000000e4400000000000054010000611b0000631b0000

2 | 592744 | 592744 | (3407000,2) | 110000000111000000000000 | \xd110000000000000104400000000000040010000611b0000631b0000

3 | 592744 | 592744 | (3407000,3) | 110000000111000000000000 | \xd11000000000000011440000000000007c010000611b0000631b0000

t_data 是数据。 lp 是项目列表中的元组索引。 t_xmin 和 t_xmax 是交易 ID。并且 t_ctid 是指向元组本身的元组的点。如果您的元组中有空值,则 t_bits 是 NULL 位图。

首先查看t_min = t_max,并且t_ctid(page_index, tuple_id)和lp是否相同。如果是这样,请检查 t_xmin 是否与您的交易 ID 相同。如果是,请检查数据。

注意字节序和空位图。就我而言,它是大端(LSB 优先)。

在我的示例中,第一行是有效的。第一个 BIGINT(8 字节 16 十六进制数)是我正在查找的排序 ID。所以第一行的数据是

\xd110000000000000

转换为 0x101d(检查字节序)--> 4305

我知道我最大的 id 是 18209,smallest_id 是 2857。我把工作分成 8 个部分,所以

(18209 - 2857) / 8 = 1919

这是我运行的第一部分。所以

2857 + 1919 = 4776

这意味着我的子作业从 2857 id 开始,目前在 4305。如果它达到 4776,则此线程已完成!

这是

(4305 - 2857)/ 1919 = 75.5% 完成


限制

这不适用于哈希值更新。在我的例子中,id 恰好作为 pkey 顺序排序。规划器触发顺序读取。如果规划器正在执行某种 btree 索引扫描以进行更新,这也应该有效。

如果您有兴趣按索引顺序排列物理行,请查看CLUSTER

同样,这种方法并不准确。并且上面强调的假设。如果在程序中使用,应稀疏使用以防止磁盘 I/O 的额外开销

【讨论】:

    【解决方案3】:

    不确定这是否是人们正在寻找的确切答案,但我制作了一个简单的函数,它通过随时间测量其页面大小来报告表格插入的当前状态。这不是了解正在发生的事情的直接窗口,但它可以很好地估计正在发生什么/是否发生了什么。这也是衡量底线的可靠指标(一张桌子被“填满”的速度)。

    该函数返回一个表名列表,其中包含表及其所有相关索引的当前大小(以字节和人类可读单位为单位)和增长率。​​strong>

    ** 奖励:它还包括临时文件活动

    我特别使用它来查看加载表的进度以及加载速度,这有助于估计需要多长时间(尽管对于大负载来说线性越来越少)。

    这是一个可移植的函数:

    CREATE OR REPLACE FUNCTION table_build_monitor(
        IN table_or_schema_list TEXT[] DEFAULT NULL
    ,   IN sample_period INT DEFAULT 10
    )
    RETURNS TABLE (
        table_name TEXT
    ,   table_size TEXT
    ,   index_size TEXT
    )
    AS
    $$
    DECLARE
        table_list TEXT[];
        schema_list TEXT[];
    BEGIN
    
    DROP TABLE IF EXISTS table_sizes_loop;
    CREATE TEMP TABLE table_sizes_loop (
        table_name_loop TEXT
    ,   table_size_bytes BIGINT
    ,   indexes_size_bytes BIGINT
    )
    ;
    
    select
        array_remove(array_agg(case when split_part(poo, '.',2) = '*' then split_part(poo, '.',1) else NULL end), NULL::TEXT)
    ,   array_remove(array_agg(case when split_part(poo, '.',2) = '*' then NULL else poo end), NULL::TEXT)
    FROM unnest(array[table_or_schema_list]) poo
    INTO schema_list, table_list
    ;
    
    INSERT INTO table_sizes_loop
    
    SELECT
        pg_tables.schemaname||'.'|| pg_tables.tablename as table_name
    ,   pg_relation_size(pg_tables.schemaname||'.'|| pg_tables.tablename) AS table_size_bytes
    ,   pg_indexes_size(pg_tables.schemaname||'.'|| pg_tables.tablename) AS indexes_size_bytes
    FROM pg_tables
    WHERE
        pg_tables.schemaname = ANY(schema_list)
    OR  (pg_tables.schemaname||'.'|| pg_tables.tablename)::text = ANY(table_list)
    
    UNION
    
    SELECT
        'temp_files'
    ,   temp_bytes
    ,   NULL
    FROM pg_stat_database
    WHERE
        datname = current_database()
    ;
    
    PERFORM pg_sleep(sample_period);
    
    RETURN QUERY
    
    with
        base AS
    (
    SELECT
        pg_tables.schemaname||'.'|| pg_tables.tablename as table_name_loop
    ,   pg_relation_size(pg_tables.schemaname||'.'|| pg_tables.tablename) AS table_size_bytes
    ,   pg_indexes_size(pg_tables.schemaname||'.'|| pg_tables.tablename) AS indexes_size_bytes
    
    FROM pg_tables
    WHERE
        pg_tables.schemaname::text = ANY(schema_list)
    OR  (pg_tables.schemaname||'.'|| pg_tables.tablename)::text = ANY(table_list)
    
    UNION
    
    SELECT
        'temp_files'
    ,   temp_bytes
    ,   NULL
    FROM pg_stat_database
    WHERE
        datname = current_database()
    
    )
    SELECT
        table_name_loop
    ,   CASE WHEN table_name_loop = 'temp_files' THEN
            pg_size_pretty((base.table_size_bytes - tsl.table_size_bytes)/sample_period) || '/s'
        ELSE
                base.table_size_bytes
            || ' (' || pg_size_pretty((base.table_size_bytes))
            || ') - ' || pg_size_pretty((base.table_size_bytes - tsl.table_size_bytes)/sample_period) || '/s'
        END as table_size
    ,       base.table_size_bytes
        || ' (' || pg_size_pretty((base.indexes_size_bytes))
        || ') - ' || pg_size_pretty((base.indexes_size_bytes - tsl.indexes_size_bytes)/sample_period) || '/s'
        as table_size
    FROM table_sizes_loop tsl
    JOIN base USING (table_name_loop)
    ORDER BY base.table_size_bytes DESC
    ;
    
    END
    $$
    LANGUAGE plpgsql
    ;
    

    要查看它,请使用如下所示的 select 语句,为整个架构传递一个模式限定表或类似“schema.*”之类的列表 - 以及可选的采样周期(默认为 10 秒)。

    select * from table_build_monitor('{public.*}', 3);
    

    【讨论】:

    • 不完全是一个简单的查询
    【解决方案4】:

    没有。无法跟踪查询的“实时”进度。理论上,系统可以将顶级进度与查询计划进行比较,并发出某种百分比读数。在实践中,我怀疑它是否非常准确,我怀疑性能影响是否值得。

    【讨论】:

    【解决方案5】:

    您可以在表中添加一个update_time 列,保存上次更新的值。如果您知道哪些记录应该受到影响,那么您还可以将它们的update_time 设置为当前时间,当您检查进度并且您知道受影响的行数时,您可以选择受影响的记录数@ 987654323@ 比您开始更新的时间要新。具有“新”update_time 的受影响行数/要更新的记录数 * 100 为您提供进度百分比。

    【讨论】:

    • 如果这是在单个语句中完成的,则在提交所有更改之前不会看到任何更改。
    • 没错,这就是为什么将更新分成批次是个好主意。例如,如果您必须更新 100 000 条记录,然后将其分成 100 个批次,每个批次适用于 1000 条记录,您将看到它们的百分比变化。
    • 没错,但如果他愿意并且能够这样做,他可以免费获得进度指示,而无需添加额外的列,因为是客户端发出批次。跨度>
    猜你喜欢
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多