У меня есть запрос, который занимает ~ 160 секунд, он просматривает записанные просмотры пользователей для данного связанного заголовка и возвращает те, у которых было наибольшее количество просмотров за последнюю неделю.

SELECT issue_types.name AS issue_type_name, titles.name AS title_name, titles.primary_issue_id, titles.id, title_views.title_id, COUNT(title_views.title_id) AS views,
CASE WHEN titles.id = 14313 THEN COUNT(title_views.title_id) - 1
WHEN titles.id = 268 THEN COUNT(title_views.title_id) - 100
WHEN titles.id = 1331 THEN COUNT(title_views.title_id) - 400
WHEN titles.id = 12722 THEN COUNT(title_views.title_id) - 200
WHEN titles.id = 4605 THEN COUNT(title_views.title_id) - 200
WHEN titles.id = 13365 THEN COUNT(title_views.title_id) - 500
WHEN titles.id = 3714 THEN COUNT(title_views.title_id) - 500
ELSE views
END AS calcViews FROM titles
LEFT JOIN issues ON issues.id = titles.primary_issue_id
LEFT JOIN title_views ON title_views.title_id = titles.id
LEFT JOIN issue_types ON issue_types.id = issues.issue_type_id
WHERE titles.deleted = FALSE AND titles.primary_issue_id IS NOT NULL
AND title_views.created > DATE_SUB(NOW(), INTERVAL 1 WEEK)
GROUP BY title_views.title_id, issue_types.name, titles.name, titles.primary_issue_id, titles.id
ORDER BY calcViews DESC, titles.name ASC
LIMIT 0, 50;

Вот некоторая информация о таблице title_views:

CREATE TABLE `title_views` (
  `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `title_id` bigint(11) unsigned NOT NULL,
  `created` datetime DEFAULT CURRENT_TIMESTAMP,
  `user_id` bigint(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_client_title_views_titles` (`title_id`),
  KEY `user_id` (`user_id`),
  KEY `created` (`created`),
  CONSTRAINT `FK_client_title_views_titles` FOREIGN KEY (`title_id`) REFERENCES `titles` (`id`),
  CONSTRAINT `title_views_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17978207 DEFAULT CHARSET=utf8               

Ряды: 2,442,396

Данные 133,7 МиБ Индекс 113,2 МиБ Всего 246,9 МиБ

Вот информация о titles:

CREATE TABLE `titles` (
  `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `needs_indexing` tinyint(1) NOT NULL DEFAULT '1',
  `primary_issue_id` bigint(11) unsigned DEFAULT NULL,
  `created` datetime DEFAULT CURRENT_TIMESTAMP,
  `deleted` tinyint(1) NOT NULL DEFAULT '0',
  `views` int(11) NOT NULL DEFAULT '0',
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_titles_issues` (`primary_issue_id`),
  FULLTEXT KEY `name` (`name`),
  CONSTRAINT `FK_titles_issues` FOREIGN KEY (`primary_issue_id`) REFERENCES `issues` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24010 DEFAULT CHARSET=utf8

Ряды: 23500

Данные 2,5 МиБ Индекс 2 МиБ Всего 4,5 МиБ

При удалении проверки, было ли представление создано на прошлой неделе, оно снижается до 10 секунд, что все еще не очень хорошо, но намного лучше, чем текущее.

Я не уверен, как лучше оптимизировать это, кроме еженедельного кеша запросов, поскольку я не администратор БД, но это, очевидно, вызывает чрезвычайно плохой UX, и мне нужно его оптимизировать или найти лучший способ делать то, что он пытается сделать .

0
Matthew 23 Ноя 2020 в 23:56

1 ответ

Лучший ответ

Есть несколько возможных подходов к проблемам оптимизации в mysql

Вот как я бы попытался оптимизировать запрос

  1. Добавить индекс на view_counts.created
  2. Перепишите запрос на 1-е количество сборов в зависимости от даты, а затем к нему присоедините данные, что ускорит поиск и сопоставление данных.
  3. Добавить составной индекс на (primary_issue_id, deleted), а также на (delete, primary_issue_id)

Тогда я бы переписал запрос на это:

select 
    counts.title_id,
    issue_types.name AS issue_type_name,
    titles.name AS title_name,
    titles.primary_issue_id 
    CASE 
      WHEN counts.title_id = 14313 THEN counts.c -1
      WHEN counts.title_id = 268 THEN counts.c - 100
      WHEN counts.title_id = 1331 THEN counts.c - 400
      WHEN counts.title_id = 12722 THEN counts.c - 200
      WHEN counts.title_id = 4605 THEN counts.c - 200
      WHEN counts.title_id = 13365 THEN counts.c - 500
      WHEN counts.title_id = 3714 THEN counts.c - 500
    ELSE counts.c as view_counts
from 
  (
    select
      tv.title_id, count(tv.title_id) as c
    from
      title_views as tv 
    where 
      tv.created > DATE_SUB(now(), INTERVAL 1 WEEK)
    group by tv.id
  ) as counts
LEFT JOIN issues ON issues.id = titles.primary_issue_id
left join issue_types ON issue_types.id = issues.issue_type_id
WHERE
   titles.deleted = FALSE
     AND
   titles.primary_issue_id IS NOT NULL

-- here just add groupings and ordering (i dont know which setting you are using)
  1. Я бы выполнил этот запрос, добавив к нему слово explain, чтобы получить информацию о том, как mysql видит оптимизацию для запроса (не должно быть полного сканирования таблицы - было бы здорово, если бы вы могли прикрепить вывод объяснения для этого запроса)
  2. В этом выводе объяснения должны быть записаны используемые индексы - после этого я бы удалил неиспользованный из пункта 3.
1
Seti 23 Ноя 2020 в 22:02