Условная эффективность агрегации
Давайте две таблицы.
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 ответ
В ваших скриптах инициализации есть синтаксические ошибки, я изменил их следующим образом:
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 на самом деле не улучшает производительность этого запроса, а только обеспечивает лучшую читаемость.