Почему намного медленнее выполнять в цикле функцию plpgsql, чем выполнять независимо для каждого цикла без функции? - PullRequest
0 голосов
/ 24 октября 2019

У меня есть функция pl-pgsql для поиска информации о гео-IP для нескольких адресов. Это в основном делает цикл в списке адресов и ищет один за другим. Я ожидаю, что время выполнения будет похоже на общее время выполнения, при котором вручную выполняется поиск IP-адресов без функции. Но это оказывается намного медленнее. Почему?

Я пытался проверить с двумя IP-адресами 27.111.12.93 и 52.23.111.175. И я также записал / выбрал время выполнения для каждого цикла в функции. С другой стороны, я также запускал вручную для каждого из двух IP-адресов с почти одинаковым SQL и проверял время выполнения с помощью explain analyze. Я подозревал, что кеш влияет, поэтому я протестировал несколько раз для обоих, предположим, что коэффициент кеша должен одинаково повлиять на обе стороны. (Кстати, тип данных ipaddress относится к расширению Postgres ip4r.)

Код функции находится здесь:

CREATE OR REPLACE FUNCTION test(ips ipaddress[])
RETURNS TABLE(
    latitude character varying(16),
    longitude character varying(16),
    country_name character varying(60),
    subdivision_1_iso_code character varying(4),
    city_name character varying(100),
    ip ipaddress,
    ts interval
) AS $$
    DECLARE ip ipaddress;
    tstamp timestamptz;
BEGIN
    FOR ip IN SELECT DISTINCT ips_t.ip FROM (SELECT unnest(ips) ip) ips_t
    LOOP
        tstamp := clock_timestamp();
        RETURN QUERY
            SELECT b.latitude, b.longitude, l.country_name, l.subdivision_1_iso_code, l.city_name, ip, clock_timestamp() - tstamp
              FROM blocks4 b
                  INNER JOIN locations l USING (geoname_id)
             WHERE b.network >>= ip
             LIMIT 1;
    END LOOP;
    RETURN;
END;
$$ LANGUAGE plpgsql STABLE STRICT;

И я вызываю его так:

select * from test(
ARRAY['27.111.12.93'::ipaddress, '52.23.111.175'::ipaddress]
);

Тогда код для проверки каждого IP-адреса вручную выглядит следующим образом:

SELECT b.latitude, b.longitude, l.country_name, l.subdivision_1_iso_code, l.city_name, '27.111.12.93'::ipaddress, clock_timestamp()
      FROM blocks4 b
          INNER JOIN locations l USING (geoname_id)
     WHERE b.network >>= '27.111.12.93'::ipaddress
     LIMIT 1;

Результат выполнения функции:

 latitude | longitude | country_name  | subdivision_1_iso_code | city_name |      ip       |       ts
----------+-----------+---------------+------------------------+-----------+---------------+-----------------
 -41.0000 | 174.0000  | New Zealand   |                        |           | 27.111.12.93  | 00:00:00.46761
 39.0481  | -77.4728  | United States | VA                     | Ashburn   | 52.23.111.175 | 00:00:00.485468
(2 rows)

Как видите, время выполнения для каждогоIP-адрес превышает 400 мс.

Затем проверяйте их вручную по одному, время запроса для IP 27.111.12.93 равно

Planning Time: 0.213 ms
Execution Time: 34.718 ms

и для 52.23.111.175:

Planning Time: 0.217 ms
Execution Time: 91.154 ms

Почему время выполнения функции намного дольше, если учесть, что они в основном делают одно и то же?

1 Ответ

0 голосов
/ 24 октября 2019

На первый взгляд, функция PL / pgSQL выполняет больше запросов.

  1. SELECT * FROM test (...)
  2. SELECT unnest (ips) ip
  3. SELECT DISTINCT ips_t.ip FROM # 2 выше

Затем следует выполнить следующий запрос для каждого IP , возвращаемого # 3. Число N вызовов # 3.

SELECT b.latitude, b.longitude, l.country_name, l.subdivision_1_iso_code, l.city_name, ip, clock_timestamp() - tstamp
FROM blocks4 b
  INNER JOIN locations l USING (geoname_id)
WHERE b.network >>= ip
LIMIT 1;

Таким образом, для всего двух IP-адресов вы выполняете несколько операторов SQL, не говоря уже о сохранении состояния для временной метки и итерации по вложенному результирующему набору.

Если вместо этого вы объединили несколько ...

CREATE OR REPLACE FUNCTION test(ips ipaddress[])
RETURNS TABLE(
    latitude character varying(16),
    longitude character varying(16),
    country_name character varying(60),
    subdivision_1_iso_code character varying(4),
    city_name character varying(100),
    ip ipaddress,
    ts interval
) AS $$
  BEGIN
    SELECT DISTINCT ON (ip)
      b.latitude, b.longitude, l.country_name, l.subdivision_1_iso_code, l.city_name, ip, clock_timestamp()
    FROM unnest(ips) AS ip
      INNER JOIN blocks4 b ON (b.network >>= ip)
      INNER JOIN locations l USING (geoname_id);
    RETURN;
  END;
$$ LANGUAGE plpgsql STABLE STRICT;

и, возможно, добавили встроенную оконную функцию для отслеживания разницы во времени между циклами с помощью LAG () , я думаю, вы 'Вы найдете, что ваше время на обработку заметно сократилось.

...