Insert-select получает лучший план, когда добавлено предложение limit - PullRequest
0 голосов
/ 17 января 2019

Это сервер, на котором я работаю

select version();
                                                 version
---------------------------------------------------------------------------    
PostgreSQL 10.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
(1 row)

Я начал с написания select (ext.t_event и ext.t_event_data - две сторонние таблицы, которые oracle_fdw (версия 1.1) берет из удаленной базы данных oracle)

select 
  te.id_data, 
  te.id_device, 
  te.date_write, 
  te.date_event, 
  ted.i_inout, 
  ted.value
from ext.t_event te, ext.t_event_data ted 
where te.id_device =2749651 
  and te.date_event >= '2019-01-16'and te.date_event < '2019-01-17' 
  and te.id_data=ted.id_data;

Требуется около 10 секунд, чтобы получить весь набор записей (3600 записей).

Но потом я превратил выбор в выбор вставки

insert into stg_data
select 
  te.id_data, 
  te.id_device, 
  te.date_write, 
  te.date_event, 
  ted.i_inout, 
  ted.value
from ext.t_event te, ext.t_event_data ted 
where te.id_device =2749651 
  and te.date_event >= '2019-01-16'and te.date_event < '2019-01-17' 
  and te.id_data=ted.id_data;

и я был вынужден убить запрос, он работал более 30 минут!

После нескольких часов борьбы и отчаянных попыток я решил попробовать этот

insert into stg_data
select 
  te.id_data, 
  te.id_device, 
  te.date_write, 
  te.date_event, 
  ted.i_inout, 
  ted.value
from ext.t_event te, ext.t_event_data ted 
where te.id_device =2749651 
  and te.date_event >= '2019-01-16'and te.date_event < '2019-01-17' 
  and te.id_data=ted.id_data
  limit 5000;

и ... неожиданный сюрприз за 20 секунд. У меня весь набор записей хранится в stg_data.

Чтобы лучше понять различия, я решил проанализировать планы.

ВЫБРАТЬ БЕЗ ОГРАНИЧЕНИЯ

 Foreign Scan  (cost=10000.00..20000.00 rows=1000 width=548)
   Oracle query: SELECT /*eb01c463a72c3b6350f86f5db25e1353*/ r1."ID_DATA",
   r1."ID_DEVICE", r1."DATE_WRITE", r1."DATE_EVENT", r2."I_INOUT",
   r2."VALUE" FROM ("DISPATCH"."T_EVENT" r1 INNER JOIN
   "DISPATCH"."T_EVENT_DATA" r2 ON (r1."ID_DATA" = r2."ID_DATA") AND
  (r1."DATE_EVENT" >= (CAST ('2019-01-16 00:00:00.000000 AD' AS
  TIMESTAMP))) AND (r1."DATE_EVENT" < 
  (CAST ('2019-01-17 00:00:00.000000 AD' AS TIMESTAMP))) 
  AND (r1."ID_DEVICE" = 2749651))

ВЫБРАТЬ С ОГРАНИЧЕНИЕМ

 Limit  (cost=10000.00..20000.00 rows=1000 width=548)
   ->  Foreign Scan  (cost=10000.00..20000.00 rows=1000 width=548)
      Oracle query: SELECT /*eb01c463a72c3b6350f86f5db25e1353*/
      r1."ID_DATA", r1."ID_DEVICE", r1."DATE_WRITE", r1."DATE_EVENT", 
      r2."I_INOUT", r2."VALUE" FROM ("DISPATCH"."T_EVENT" r1 INNER 
      JOIN "DISPATCH"."T_EVENT_DATA" r2 ON (r1."ID_DATA" = r2."ID_DATA")
      AND (r1."DATE_EVENT" >= (CAST ('2019-01-16 00:00:00.000000 AD' AS 
      TIMESTAMP))) AND (r1."DATE_EVENT" < (CAST ('2019-01-17
      00:00:00.000000 AD' AS TIMESTAMP))) AND (r1."ID_DEVICE" = 2749651))

Таким образом, он в основном отправляет тот же запрос в Oracle и применяет ФИЛЬТР локально, как только выборка завершена.

План INSER-SELECT выглядит одинаково? NOPE!

INSERT_SELECT с LIMIT

Insert on stg_data_hist  (cost=10000.00..20010.00 rows=1000 width=548)
   ->  Limit  (cost=10000.00..20000.00 rows=1000 width=548)
         ->  Foreign Scan  (cost=10000.00..20000.00 rows=1000 width=548)
               Oracle query: SELECT /*eb01c463a72c3b6350f86f5db25e1353*/ 
               r1."ID_DATA", r1."ID_DEVICE", r1."DATE_WRITE", 
               r1."DATE_EVENT", r2."I_INOUT", r2."VALUE" FROM 
               ("DISPATCH"."T_EVENT" r1 INNER JOIN 
               "DISPATCH"."T_EVENT_DATA" r2 ON (r1."ID_DATA" = 
               r2."ID_DATA") AND (r1."DATE_EVENT" >= (CAST ('2019-01-16 
               00:00:00.000000 AD' AS TIMESTAMP))) AND (r1."DATE_EVENT" < 
               (CAST('2019-01-17 00:00:00.000000 AD' AS TIMESTAMP))) AND 
               (r1."ID_DEVICE" = 2749651))

INSERT-SELECT no LIMIT предложение

Insert on stg_data_hist  (cost=30012.50..40190.00 rows=5000 width=548)
 ->  Hash Join  (cost=30012.50..40190.00 rows=5000 width=548)
       Hash Cond: (te.id_data = ted.id_data)
     ->  Foreign Scan on t_event te  (cost=10000.00..20000.00 rows=1000 width=28)
           Oracle query: SELECT /*93379c271b3f1bc08a1dbb94fb89f739*/ 
           r3."ID_DATA", r3."ID_DEVICE", r3."DATE_WRITE", r3."DATE_EVENT" 
           FROM "DISPATCH"."T_EVENT" r3 WHERE (r3."DATE_EVENT" >= 
           (CAST ('2019-01-16 00:00:00.000000 AD' AS TIMESTAMP))) AND 
           (r3."DATE_EVENT" < (CAST ('2019-01-17 00:00:00.000000 AD' AS 
           TIMESTAMP))) AND (r3."ID_DEVICE" = 2749651)
       ->  Hash  (cost=20000.00..20000.00 rows=1000 width=528)
           ->  Foreign Scan on t_event_data ted  
                  (cost=10000.00..20000.00 rows=1000 width=528)
                 Oracle query: SELECT /*21c8741f2fa8a8d13d037c3191e8ac96*/ 
                    r4."ID_DATA", r4."I_INOUT", r4."VALUE" FROM 
                    "DISPATCH"."T_EVENT_DATA" r4

И это объясняет, почему это занимает ваааааай дольше, чем другие. Он извлекает отфильтрованные по дате записи из одной внешней таблицы, полный набор из второй внешней таблицы и выполняет локальное объединение. Это займет много времени !! Это несколько миллионов записей против нескольких тысяч.

И наконец два моих вопроса

1) Я хочу иметь первый план, но избавлюсь от предложения LIMIT (пошли дрожи по спине :-)). Как бы Вы это сделали? Я не имею в виду применять фильтры к ext.t_event_data, кроме как из условия соединения.

2) Почему два плана INSERT-SELECT выглядят такими разными, даже если два плана SELECT выглядят одинаково?

Спасибо за чтение и хорошего дня

1 Ответ

0 голосов
/ 18 января 2019

Похоже, что планировщик думает, что он просто получит несколько тысяч строк в любом случае, что явно не так, убедитесь, что статистика для внешних таблиц актуальна, запустив 'ANALYZE ext.t_event' и то же самое для ext.t_event_data потому что:

https://github.com/laurenz/oracle_fdw

PostgreSQL не будет автоматически собирать статистику для внешних таблиц с помощью демона autovacuum.

Имейте в виду, что при анализе внешней таблицы Oracleприведет к полному последовательному сканированию таблицы.Вы можете использовать опцию таблицы sample_percent, чтобы ускорить это, используя только образец таблицы Oracle.

Соединения - это , отправленные в Oracle в случае выбора и ввставьте регистр, если используется лимит, поэтому единственная причина, по которой я могу его не использовать при вставке без лимита, - это отсутствие точной статистики таблиц.Вы можете попробовать переписать запрос на вставку как CTE (не проверял этот запрос по понятным причинам):

WITH foreign_data AS (
select 
  te.id_data, 
  te.id_device, 
  te.date_write, 
  te.date_event, 
  ted.i_inout, 
  ted.value
from ext.t_event te, ext.t_event_data ted 
where te.id_device =2749651 
  and te.date_event >= '2019-01-16'and te.date_event < '2019-01-17' 
  and te.id_data=ted.id_data
)

insert into stg_data from foreign_data

Вы также можете попробовать переписать запрос как явное внутреннее соединение вместо того, чтобы иметь условие соединения впредложение where (te.id_data = ted.id_data).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...