Условная эффективность агрегации - PullRequest
0 голосов
/ 08 июля 2019

Давайте две таблицы.

A(id int primary key, groupby int, fkb int, search int, padding varchar(1000))
B(id int primary key, groupby int, search int)

Они созданы с использованием следующих сценариев. Первая таблица большая (1 млн. Строк), а вторая меньше (10 тыс. Строк).

CREATE  TABLE A(
  id int not null primary key, 
  groupby int null, 
  fkb int null, 
  search int null,
  padding varchar(1000) null
)  AS
WITH x AS
(
  SELECT 0 n FROM dual
  union all
  SELECT 1 FROM dual
  union all
  SELECT 2 FROM dual
  union all
  SELECT 3 FROM dual
  union all
  SELECT 4 FROM dual
  union all
  SELECT 5 FROM dual
  union all
  SELECT 6 FROM dual
  union all
  SELECT 7 FROM dual
  union all
  SELECT 8 FROM dual
  union all
  SELECT 9 FROM dual
), t1 AS
(
  SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n + 10000 * tenthousands.n + 100000 * hundredthousands.n as id
  FROM x ones,     x tens,      x hundreds,       x thousands,       x tenthousands,       x hundredthousands
), t2 AS
(
    SELECT  id,
            mod(id, 100) groupby
    FROM t1
)
SELECT  cast(id as int) id,
        cast(groupby as int) groupby,
        cast(mod(orderby, 9173) as int) fkb,
        cast(mod(id, 911) as int) search
FROM t2;

CREATE  TABLE B(
  id int not null primary key, 
  groupby int null, 
  search int null
) AS
WITH x AS 
(
  SELECT 0 n FROM dual
  union all 
  SELECT 1 FROM dual
  union all 
  SELECT 2 FROM dual
  union all 
  SELECT 3 FROM dual
  union all 
  SELECT 4 FROM dual
  union all 
  SELECT 5 FROM dual
  union all 
  SELECT 6 FROM dual
  union all 
  SELECT 7 FROM dual
  union all 
  SELECT 8 FROM dual
  union all 
  SELECT 9 FROM dual  
), t1 AS
(
  SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as id  
  FROM x ones,     x tens,      x hundreds,       x thousands       
)
SELECT  cast(id as int) id,
        cast(mod(id + floor(100000 / (id+1)) , 100) as int) groupby,
        cast(mod(id, 901) as int) search,
        rpad(concat('Value ', id), 1000, '*') as padding
FROM t1;

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

SELECT  B.groupby,
       count(CASE WHEN A.search = 1 THEN 1 END) as search1,
       count(CASE WHEN A.search = 900 THEN 1 END) as search2
FROM B
LEFT JOIN A ON A.fkb = B.id
WHERE B.search < 10
GROUP BY B.groupby

Можно ли переписать запрос, который выполняется максимум за 2 минуты? Я пробовал много разных переписываний, однако каждый продолжает работать без конца в течение нескольких минут. Я установил для памяти виртуальной машины Java значение 4 ГБ (-Xmx4G).

Если я попробую тот же тест в MySQL и запрос будет обработан менее чем за 10 секунд.

1 Ответ

1 голос
/ 09 июля 2019

В ваших скриптах инициализации есть синтаксические ошибки, я изменил их следующим образом:

CREATE  TABLE A(
  id int not null primary key, 
  groupby int null, 
  fkb int null, 
  search int null,
  padding varchar(1000) null
)  AS
SELECT  cast(x as int) id,
        cast(mod(x, 100) as int) groupby,
        cast(mod(mod(x, 100), 9173) as int) fkb,
        cast(mod(x, 911) as int) search,
        rpad(concat('Value ', x), 1000, '*') as padding
FROM SYSTEM_RANGE(0, 999999);

CREATE  TABLE B(
  id int not null primary key, 
  groupby int null, 
  search int null
) AS
SELECT  cast(x as int) id,
        cast(mod(x + floor(100000 / (x+1)), 100) as int) groupby,
        cast(mod(x, 901) as int) search
FROM SYSTEM_RANGE(0, 9999);

Для простоты я также использовал специфический для H2 SYSTEM_RANGE().

Команда EXPLAIN с вашим запросом показывает следующий план выполнения

SELECT
    "B"."GROUPBY",
    COUNT(CASE WHEN ("A"."SEARCH" = 1) THEN 1 END) AS "SEARCH1",
    COUNT(CASE WHEN ("A"."SEARCH" = 900) THEN 1 END) AS "SEARCH2"
FROM "PUBLIC"."B"
    /* PUBLIC.B.tableScan */
    /* WHERE B.SEARCH < 10
    */
LEFT OUTER JOIN "PUBLIC"."A"
    /* PUBLIC.A.tableScan */
    ON "A"."FKB" = "B"."ID"
WHERE "B"."SEARCH" < 10
GROUP BY "B"."GROUPBY"

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

Я думаю, вам нужно здесь ограничение.

ALTER TABLE A ADD CONSTRAINT A_FKB_FK FOREIGN KEY(FKB) REFERENCES B(ID);

С таким ограничением план выполнения намного лучше:

SELECT
    "B"."GROUPBY",
    COUNT(CASE WHEN ("A"."SEARCH" = 1) THEN 1 END) AS "SEARCH1",
    COUNT(CASE WHEN ("A"."SEARCH" = 900) THEN 1 END) AS "SEARCH2"
FROM "PUBLIC"."B"
    /* PUBLIC.B.tableScan */
    /* WHERE B.SEARCH < 10
    */
LEFT OUTER JOIN "PUBLIC"."A"
    /* PUBLIC.A_FKB_FK_INDEX_4: FKB = B.ID */
    ON "A"."FKB" = "B"."ID"
WHERE "B"."SEARCH" < 10
GROUP BY "B"."GROUPBY"

С ограничением ваш запрос требует около 11 секунд на моем старом ПК.

Вы также можете использовать COUNT(*) FILTER (WHERE A.search = 1) в своем запросе с H2, но такой запрос не будет совместим с MySQL, MySQL пока не поддерживает стандартное предложение FILTER для SQL: 2003, а предложение FILTER на самом деле не повышает производительность этот запрос обеспечивает лучшую читаемость.

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