SQL оптимизация запросов с 3 объединениями - PullRequest
1 голос
/ 24 апреля 2020

Я изучаю акт оптимизации запросов с точки зрения времени выполнения. У меня есть 3 таблицы, которые посты, комментарии и пользователи. Приведенный ниже запрос отлично работает, когда возвращает пользователей, которые сделали больше комментариев, чем сообщений за 2010 год, и их количество . Я считаю, что это можно оптимизировать, и я был бы признателен за оптимизированный код с объяснением.

Информация по каждой таблице

  • Таблица пользователей; Имеет 40325 строк с индексацией по первичному ключу (Id).
  • Таблица сообщений; Имеет 91986 строк с индексацией по первичному ключу (PostId), а также OwnerUserId, который является внешним ключом, который ссылается на таблицу пользователей.
  • Таблица комментариев - имеет 174305 строк, с индексацией по первичному ключу, UserId (для таблицы пользователей) и PostId (для таблицы сообщений).
SELECT pos_table.user_ID, pos_table.Username, comms, pos from
    (SELECT
    users.Id as 'user_ID', users.DisplayName as 'Username', count(posts.Id) as pos
    FROM
    users
    INNER JOIN posts ON posts.OwnerUserId = users.Id
    WHERE YEAR(posts.CreationDate) = 2010
    group by users.Id
    ) pos_table
    JOIN
    (SELECT
    users.Id as 'user_ID', users.DisplayName as 'Username', count(comments.Id) as
    comms
    FROM
    users
    INNER JOIN comments ON comments.UserId = users.Id
    WHERE YEAR(comments.CreationDate) = 2010
    group by users.Id
    ) comms_table
    on pos_table.user_ID = comms_table.user_ID
    HAVING comms > pos
    order by user_ID
    limit 50;

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

Результат вышеупомянутого запроса и вложение моего запроса EXPLAIN: enter image description here enter image description here

1 Ответ

3 голосов
/ 24 апреля 2020

Одна вещь выскакивает на меня. В обоих ваших подзапросах есть такая строка.

          WHERE YEAR(posts.CreationDate) = 2010

Вы вызываете функцию для значения столбца. Это не sargeable . Он не позволяет MySQL использовать индекс для этого столбца и вместо этого требует полного сканирования. (MySQL и другие СУБД все еще слишком глупы, чтобы знать, что YEAR(timestamp) может быть удовлетворено сканированием диапазона индекса.)

Поэтому измените эти ГДЕ на подобные вещи.

          WHERE posts.CreationDate >= '2010-01-01'
            AND posts.CreationDate <  '2010-01-01' + INTERVAL 1 YEAR

и поместите индекс в столбцы CreationDate в ваших таблицах сообщений и комментариев. Затем планировщик запросов может выполнить произвольный поиск в индексе для первой соответствующей строки, а затем последовательно прочитать его до последней соответствующей строки. Это называется сканированием диапазона индекса, и оно гораздо более эффективно, чем сканирование полной таблицы.

РЕДАКТИРОВАНИЕ Вам нужны следующие индексы:

CREATE INDEX date_user ON posts ( CreationDate, OwnerUserId );
CREATE INDEX date_user ON comments ( CreationDate, UserID);

Я предлагаю вам рефакторинг вашего запроса, чтобы сделать ваши подзапросы, где вся работа выполняется, быстрее.

Это должны быть подзапросы. Каждый из них выдает количество элементов для каждого пользователя в желаемом диапазоне времени.

   SELECT OwnerUserId, COUNT(*) posts
     FROM posts
    WHERE CreationDate >= '2010-01-01'
      AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
    GROUP BY OwnerUserId

   SELECT UserId, COUNT(*) comments
     FROM comments
    WHERE CreationDate >= '2010-01-01'
      AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
    GROUP BY UserId

Эти запросы экономят время, агрегируя (суммируя по группам) минимальный объем данных, необходимый для удовлетворения запроса. И их можно удовлетворить, выполнив быстрое сканирование диапазона индексов по предложенным мною индексам.

Затем вы можете использовать эти подзапросы в своем основном запросе, выбирая имена пользователей из таблицы users, например: this.

SELECT users.Id user_ID, users.Username, c.comments, p.posts
  FROM users
  JOIN (
       SELECT OwnerUserId, COUNT(*) posts
         FROM posts
        WHERE CreationDate >= '2010-01-01'
          AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
        GROUP BY OwnerUserId
        ) p ON users.ID = p.OwnerUserId
   JOIN (
       SELECT UserId, COUNT(*) comments
         FROM comments
        WHERE CreationDate >= '2010-01-01'
          AND CreationDate <  '2010-01-01' + INTERVAL 1 YEAR
        GROUP BY UserId
        ) c ON users.ID = c.UserId
  WHERE c.comments > p.posts
  ORDER BY users.ID
  LIMIT 50;

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

Вот полезная ссылка https://use-the-index-luke.com/

...