Получить последние записи за метку времени из большой таблицы - индекс не используется - PullRequest
0 голосов
/ 29 июня 2018

У меня есть несколько промежуточных таблиц, в которые записи вставляются / обновляются (не удаляются) регулярно.

В каждой таблице есть триггер «BEFORE UPDATE», обновляющий столбец метки времени с текущей меткой времени.

Существует процесс, периодически выполняющий выборку последних записей (дельта) из каждой промежуточной таблицы на основе отметки времени, которая хранится в управляющей таблице. Это делается с использованием материализованного представления.

Контрольная таблица обновляется с указанием максимального (временная метка), найденного в материализованных представлениях каждый раз, когда запускается вышеуказанный процесс

Контрольный стол:

id | staging_table_name | input_last_update_timestamp |
---+--------------------+-----------------------------+
 1 | stg_table1         | 2018-06-29 12:57:19         |
 2 | stg_table2         | 2018-06-29 13:52:19         |

stg_table1

id      | internal_timestamp  
--------+--------------------
6875303 | 2018-06-29 14:18:17 
6874765 | 2018-06-29 14:18:17 
6875095 | 2018-06-29 14:18:17 
6867996 | 2018-06-29 14:18:17 
6873723 | 2018-06-29 14:18:17 
6874594 | 2018-06-29 14:18:17 
6868561 | 2018-06-29 14:18:17 
6875292 | 2018-06-29 14:18:00 
6874595 | 2018-06-29 14:18:00 
6875300 | 2018-06-29 14:18:00 

Я пробовал следующие запросы, но ни один из них не использует индекс, указанный в столбце «internal_timestamp» промежуточной таблицы

Query1

SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p,
    control_staging_scm.control_table o
WHERE 
    p.internal_timestamp > o.input_last_update_timestamp 
    AND o.id = 21

Query2

SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
JOIN 
    control_staging_scm.control_table o ON p.internal_timestamp > o.input_last_update_timestamp
WHERE 
    o.id = 21

Query3

SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
WHERE 
    p.internal_timestamp > (SELECT o.input_last_update_timestamp 
                            FROM control_staging_scm.control_table o 
                            WHERE o.id = 21)

Объяснить планы:

Query 1 and 2
Nested Loop  (cost=0.03..203273.39 rows=1539352 width=12) (actual time=2013.969..2058.475 rows=520 loops=1)
  Join Filter: (p.internal_timestamp > o.input_last_update_timestamp)
  Rows Removed by Join Filter: 4615088
  Buffers: shared hit=173254
  ->  Index Scan using control_table_pkey on control_table o  (cost=0.03..4.03 rows=1 width=8) (actual time=0.011..0.014 rows=1 loops=1)
        Index Cond: (id = 21)
        Buffers: shared hit=2
  ->  Seq Scan on stg_table1 p  (cost=0.00..187106.17 rows=4618055 width=12) (actual time=0.003..419.628 rows=4615608 loops=1)
        Buffers: shared hit=173252
Planning time: 0.110 ms
Execution time: 2058.533 ms

Query 3

Seq Scan on stg_table1 p  (cost=4.03..189419.23 rows=1539352 width=12) (actual time=2020.801..2054.617 rows=675 loops=1)
  Filter: (internal_timestamp > $0)
  Rows Removed by Filter: 4614988
  Buffers: shared hit=173254
  InitPlan 1 (returns $0)
    ->  Index Scan using control_table_pkey on control_table o  (cost=0.03..4.03 rows=1 width=8) (actual time=0.013..0.014 rows=1 loops=1)
          Index Cond: (id = 21)
          Buffers: shared hit=2
Planning time: 0.155 ms
Execution time: 2054.694 ms

Когда я устанавливаю enable_seqscan = OFF, используется индекс, и производительность на порядок выше

Объяснить план (Seqscan OFF)

    Nested Loop  (cost=41794.55..225088.07 rows=1539618 width=12) (actual time=0.100..0.557 rows=407 loops=1)
  Buffers: shared hit=97
  ->  Index Scan using control_table_pkey on control_table o  (cost=0.03..4.03 rows=1 width=8) (actual time=0.010..0.011 rows=1 loops=1)
        Index Cond: (id = 21)
        Buffers: shared hit=2
  ->  Bitmap Heap Scan on stg_table1 p  (cost=41794.52..220465.18 rows=1539618 width=12) (actual time=0.085..0.317 rows=407 loops=1)
        Recheck Cond: (internal_timestamp > o.input_last_update_timestamp)
        Heap Blocks: exact=90
        Buffers: shared hit=95
        ->  Bitmap Index Scan on stg_table1_internal_timestamp_idx  (cost=0.00..41717.54 rows=1539618 width=0) (actual time=0.070..0.070 rows=407 loops=1)
              Index Cond: (internal_timestamp > o.input_last_update_timestamp)
              Buffers: shared hit=5
Planning time: 0.131 ms
Execution time: 0.631 ms

Нет необходимости упоминать, что я запускаю Analyze на промежуточном столе и соответственно установил autovacuum / autoanalyze

Так, что планировщику потребуется для использования индекса для «internal_timestamp» в промежуточной таблице?

ОБНОВЛЕНИЕ 1

Прежде чем попробовать то, что @Laurenz предложил ниже, мне было любопытно, где CTE или скалярная функция справятся с задачей.

Но, к сожалению, оптимизатор не использовал бы индекс в обоих решениях

CTE

WITH x AS (
    SELECT o.input_last_update_timestamp 
    FROM control_staging_scm.control_table o 
    WHERE o.id = 21
)
SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
WHERE 
    p.internal_timestamp > (SELECT x.input_last_update_timestamp FROM x)

СКАЛЯРНАЯ ФУНКЦИЯ

CREATE OR REPLACE FUNCTION control_staging_scm.last_update_timestamp(_table_id integer)
RETURNS timestamp without time zone
AS $function$

   SELECT o.input_last_update_timestamp FROM control_staging_scm.control_table o WHERE o.id = $1;

$function$ LANGUAGE 'sql';


SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
WHERE 
    p.internal_timestamp > (SELECT control_staging_scm.last_update_timestamp(21))

Я ожидал / надеялся, что значение (метка времени) будет вычислено и будет доступно оптимизатору до выполнения основного запроса.

Было бы неплохо, если бы кто-то указал, каково внутреннее поведение оптимизатора для указанных выше случаев!

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

РЕШЕНИЕ

По предложению @Laurenz я попытался разделить два запроса и использовать результат первого запроса в качестве параметра второго запроса.

Я сделал это, используя функцию 'plpgsql', которая возвращает таблицу

CREATE OR REPLACE FUNCTION control_staging_scm.update_stgtable1_delta_mat_view()
 RETURNS TABLE (
    trade_id int4
 ,  internal_timestamp timestamp
 )  
AS $function$

DECLARE 

last_update_timestamp_temp_var timestamp WITHOUT time ZONE;

BEGIN

    SELECT input_last_update_timestamp into last_update_timestamp_temp_var
    FROM control_staging_scm.control_table
    WHERE id=21;

    RETURN QUERY
    SELECT  p.trade_id AS tr_id,          
            p.internal_timestamp AS intr_timestamp,
    FROM staging_scm.stg_table1 p
    WHERE p.internal_timestamp > last_update_timestamp_temp_var;

END;
$function$ LANGUAGE plpgsql;


SELECT * FROM  control_staging_scm.update_stgtable1_delta_mat_view()

Объяснить план

Function Scan on update_stgtable1_delta_mat_view  (cost=0.05..3.05 rows=1000 width=640) (actual time=0.828..0.847 rows=321 loops=1)
Planning time: 0.049 ms
Execution time: 0.888 ms

Наконец, оптимизатор решил использовать индекс (см. План запроса Seqscan OFF по приведенному выше вопросу). Таким образом, мы получили в 2000 раз более быстрый запрос, совсем неплохой:)

Конечно, если вы можете предложить лучший ответ, пожалуйста, не стесняйтесь!

0 голосов
/ 02 июля 2018

Оптимизатор хорошо знает, что будет только одна подходящая строка из control_table, но он не может предсказать, какое значение будет иметь столбец input_last_update_timestamp (который известен только во время выполнения запроса), поэтому он не имеет смысла способ узнать, сколько строк результата из stg_table1 следует ожидать.

Не имея этих знаний, мы возвращаемся к оценке того, что будет выбрана треть строк, что лучше всего сделать при последовательном сканировании.

Вы можете улучшить это, разделив запрос на две части:

SELECT o.input_last_update_timestamp 
FROM control_staging_scm.control_table o 
WHERE o.id = 21;

SELECT p.id, p.internal_timestamp
FROM staging_scm.stg_table1 p
WHERE p.internal_timestamp > <result from first query>;

Тогда фактическое значение будет известно при планировании второго запроса, и PostgreSQL выберет сканирование индекса, если только несколько строк соответствуют условию.

...