Оптимизировать COUNT (*) - PullRequest
       1

Оптимизировать COUNT (*)

0 голосов
/ 06 февраля 2020

У меня есть таблица items, из которой я выбираю 40 строк за раз, упорядоченные по popularity элемента.

Оценка popularity - это просто downloads/impressions;

Запрос :

SELECT id, name
FROM items
ORDER BY (SELECT COUNT(*) FROM downloads WHERE item = items.id)/
         (SELECT COUNT(*) FROM impressions WHERE item = items.id)
 LIMIT 40;

Проблема заключается в том, что выполнение запроса занимает вечность (от 2 до 10 секунд).

На данный момент у нас 25 тыс. Элементов, 18 млн. Показов и 560 тыс. Загрузок.

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

Есть ли лучший способ оптимизировать этот запрос?

Спасибо.

Редактировать

Вот вывод EXPLAIN

id  select_type           table             type      possible_keys   key       key_len   ref                 rows    Extra
1   PRIMARY               items             ALL       NULL            NULL      NULL      NULL                20496   Using filesort
3   DEPENDENT SUBQUERY    impressions       ref       PRIMARY         PRIMARY   4         db.items.id         74      Using index
2   DEPENDENT SUBQUERY    downloads         ref       PRIMARY         PRIMARY   4         db.items.id         274     Using index

Таблицы:

CREATE TABLE `items` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(35) DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24369 DEFAULT CHARSET=utf8mb4;


CREATE TABLE `impressions` (
  `item` int(10) unsigned NOT NULL,
  `user` char(36) NOT NULL DEFAULT '',
  PRIMARY KEY (`item`,`user`),
  CONSTRAINT `impression_ibfk_1` FOREIGN KEY (`item`) REFERENCES `items` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


CREATE TABLE `downloads` (
  `item` int(10) unsigned NOT NULL,
  `user` char(36) NOT NULL DEFAULT '',
  PRIMARY KEY (`item`,`user`),
  CONSTRAINT `download_ibfk_1` FOREIGN KEY (`item`) REFERENCES `items` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Ответы [ 3 ]

1 голос
/ 07 февраля 2020

Невозможно решить с таким подходом.

Существует два решения:

  • Сохранить счетчики (по item.id) для показов и загрузок.
  • Сводные таблицы .

Счетчики Это включает добавление дополнительного столбца для каждого счетчика в таблицу items. Или построение параллельной таблицы с id и различными счетчиками. Для действительно большого количества подсчетов последний позволяет избежать некоторых противоречий между различными запросами.

Сводные таблицы Построить и постепенно увеличивать таблицы, которые суммируют такие подсчеты, плюс, возможно, другие SUMs, COUNTs и др. 1028 *. Возможно, таблица будет ежедневно дополняться информацией за предыдущий день. Затем «сумма счета», чтобы получить общую сумму; это будет намного быстрее, чем ваш текущий запрос.

Подробнее о сводных таблицах: http://mysql.rjweb.org/doc.php/summarytables

1 голос
/ 06 февраля 2020

Я думаю, что следующий запрос может решить вашу проблему:

SELECT 
    item,items.name, downloads.cnt/impressions.cnt AS rate  
FROM  (
    SELECT item, COUNT(*) AS cnt FROM downloads GROUP BY item
) AS downloads
JOIN (
    SELECT item, COUNT(*) AS cnt FROM impressions GROUP BY item
) impressions
JOIN items ON items.id = downloads.items
ORDER BY rate DESC 
LIMIT 40;

Также позаботьтесь о том, чтобы таблицы downloads и impressions были проиндексированы по полю элемента.

0 голосов
/ 07 февраля 2020

Сначала я посчитаю количество скачиваний и показов, а затем получу 40:

with d as (select item, count(*) as total from downloads group by item)
   , i as (select item, count(*) as total from impressions group by item)
   , top40 as select item from d join i using (item) order by d.total / i.total limit 40)
select *
from items
where id in
(
  select item from top40
);

Предложение WITH доступно с MySQL 8. В более ранних версиях вы работали с вместо этого подзапросы.

Поскольку item является внешним ключом в downloads и impressions, а id является первичным ключом в items, я предполагаю, что для них есть индекс. В противном случае создайте его:

create unique index idx1 on items(id);
create index idx2 on downloads(item);
create index idx3 on impressions(item);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...