MySQL: простая схема, объединение в представление и сортировка по несвязанному атрибуту вызывает невыносимое снижение производительности - PullRequest
0 голосов
/ 06 августа 2011

Я создаю модель базы данных для использования различными приложениями и различными типами серверов баз данных (хотя сейчас я в основном тестирую на MySQL и SQLite). Это действительно простая модель, которая в основном состоит из одной центральной таблицы совпадений и множества атрибутов таблиц, у которых match_id является их первичным ключом и одно другое поле (само значение атрибута). Другими словами, каждое совпадение имеет ровно по одному атрибуту каждого типа, и каждый атрибут хранится в отдельной таблице. После некоторой довольно низкой производительности во время сортировки и фильтрации по этим атрибутам (FROM matches LEFT JOIN attributes_i_want on primary index) я решил попытаться улучшить ее. Для этого я добавил индекс для каждого столбца значения атрибута. Производительность сортировки и фильтрации значительно возросла для простых запросов.

Эта простая схема в основном является требованием для приложения, поэтому она может автоматически обнаруживать и использовать атрибуты. Таким образом, чтобы создать более сложные атрибуты, которые на самом деле основаны на других результатах, я решил использовать VIEW, которые превращают одну или несколько других таблиц, которые не обязательно соответствуют атрибутоподобной схеме, в схему атрибутов. Я называю эти мета-атрибуты (они также не редактируются напрямую). Тем не менее, для приложения это все прозрачно, и поэтому оно счастливо включается в VIEW, когда захочет. Проблема: убивает производительность . Когда VIEW присоединяется без сортировки по любому атрибуту, производительность все еще приемлема, но объединение извлечения VIEW с сортировкой недопустимо медленно (порядка 1 с). Даже после прочтения большого количества руководств по индексированию и некоторых вопросов о переполнении стека я не могу помочь.

_ Предпосылки для решения: так или иначе, num_duplicates должны существовать в виде таблицы или представления со столбцами match_id и num_duplicates, чтобы они выглядели как атрибут. Я не могу изменить способ обнаружения и использования атрибутов. Поэтому, если я хочу, чтобы num_duplicates появился в приложении, он должен быть как некое представление или материализованная таблица, которая составляет таблицу num_duplicates ._

Соответствующие части схемы

Основной стол:

CREATE TABLE `matches` (
  `match_id` int(11) NOT NULL,
  `source_name` text,
  `target_name` text,
  `transformation` text,
  PRIMARY KEY (`match_id`)
) ENGINE=InnoDB;

Пример нормального атрибута (проиндексирован):

CREATE TABLE `error` (
  `match_id` int(11) NOT NULL,
  `error` double DEFAULT NULL,
  PRIMARY KEY (`match_id`),
  KEY `error_index` (`error`)
) ENGINE=InnoDB;

(все нормальные атрибуты, такие как error, в основном одинаковы)

Метаатрибут / ПРОСМОТР:

CREATE VIEW num_duplicates 
AS SELECT duplicate AS match_id, COUNT(duplicate) AS num_duplicates 
    FROM duplicate 
    GROUP BY duplicate

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

Простой запрос с индексацией по столбцам значений атрибута (часть улучшена за счет индексов)

SELECT matches.match_id, source_name, target_name, transformation FROM matches
    INNER JOIN error ON matches.match_id = error.match_id
ORDER BY error.error

(производительность по этому запросу сильно возросла из-за индекса ошибки) (время выполнения этого запроса составляет порядка 0,0001 с)

Чуть более сложные запросы и время их выполнения, включая метаатрибут (все еще плохая часть)

SELECT 
    matches.match_id, source_name, target_name, transformation, STATUS , volume, error, COMMENT , num_duplicates
FROM matches
    INNER JOIN STATUS ON matches.match_id = status.match_id
    INNER JOIN error ON matches.match_id = error.match_id
    LEFT JOIN num_duplicates ON matches.match_id = num_duplicates.match_id
    INNER JOIN volume ON matches.match_id = volume.match_id
    INNER JOIN COMMENT ON matches.match_id = comment.match_id

(время выполнения: 0,0263сек) <--- все еще приемлемо </p>

SELECT matches.match_id, source_name, target_name, transformation, STATUS , volume, error, COMMENT , num_duplicates
FROM matches
    INNER JOIN STATUS ON matches.match_id = status.match_id
    INNER JOIN error ON matches.match_id = error.match_id
    LEFT JOIN num_duplicates ON matches.match_id = num_duplicates.match_id
    INNER JOIN volume ON matches.match_id = volume.match_id
    INNER JOIN COMMENT ON matches.match_id = comment.match_id
ORDER BY error.error
LIMIT 20, 20

(время выполнения: 0,8866 с) <--- недопустимо (скорость запроса с LIMIT точно такая же, как и без LIMIT, <strong>note : если бы я мог получить версию с LIMIT, которая будет быстро, это уже было бы большой победой. Я полагаю, что это должно сканировать всю таблицу, и поэтому лимит не имеет большого значения)

ОБЪЯСНЕНИЕ последнего запроса

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

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY error   index   PRIMARY,match_id    error_index 9   NULL    53909   Using index; Using temporary; Using filesort
1   PRIMARY COMMENT eq_ref  PRIMARY PRIMARY 4   tangbig4.error.match_id 1    
1   PRIMARY STATUS  eq_ref  PRIMARY PRIMARY 4   tangbig4.COMMENT.match_id   1   Using where
1   PRIMARY matches eq_ref  PRIMARY PRIMARY 4   tangbig4.COMMENT.match_id   1   Using where
1   PRIMARY <derived2>  ALL NULL    NULL    NULL    NULL    2    
1   PRIMARY volume  eq_ref  PRIMARY PRIMARY 4   tangbig4.matches.match_id   1   Using where
2   DERIVED duplicate   index   NULL    duplicate_index 5   NULL    49222   Using index

Кстати, запрос без сортировки, который все еще выполняется приемлемо, выглядит так:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY COMMENT ALL PRIMARY NULL    NULL    NULL    49610    
1   PRIMARY error   eq_ref  PRIMARY,match_id    PRIMARY 4       tangbig4.COMMENT.match_id   1    
1   PRIMARY matches eq_ref  PRIMARY PRIMARY 4   tangbig4.COMMENT.match_id   1    
1   PRIMARY <derived2>  ALL NULL    NULL    NULL    NULL    2    
1   PRIMARY STATUS  eq_ref  PRIMARY PRIMARY 4   tangbig4.COMMENT.match_id   1    
1   PRIMARY volume  eq_ref  PRIMARY PRIMARY 4   tangbig4.matches.match_id   1       Using where
2   DERIVED duplicate   index   NULL    duplicate_index 5   NULL    49222   Using index

Вопрос

Итак, мой вопрос: может ли кто-то, кто знает больше о базах данных / MySQL, найти мне способ, которым я могу использовать / исследовать, чтобы повысить производительность моего последнего запроса.

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

РЕДАКТИРОВАТЬ : Некоторые случайные мысли, которые у меня возникли по поводу запроса:

  • ОЧЕНЬ БЫСТРО: объединение всех таблиц, кроме VIEW, сортировка
  • ACCEPTABLE: объединение всех таблиц, включая VIEW, no sorting
  • DOG SLOW: объединение всех таблиц, включая VIEW, sorting

Но: VIEW никак не влияет на сортировку, ни один из ее атрибутовили даже атрибуты в его составных таблицах используются для сортировки.Почему включение сортировки так сильно влияет на производительность?Можно ли как-то убедить базу данных сначала отсортировать, а затем просто присоединиться к ПРОСМОТРУ?Или я могу убедить его в том, что VIEW не важен для сортировки?

EDIT2 : После предложения @ace о создании VIEW и последующем присоединении не показалосьв помощь:

DROP VIEW IF EXISTS `matches_joined`;
CREATE VIEW `matches_joined` AS (
  SELECT matches.match_id, source_name, target_name, transformation, STATUS , volume, error, COMMENT
  FROM matches
  INNER JOIN STATUS ON matches.match_id = status.match_id
  INNER JOIN error ON matches.match_id = error.match_id
  INNER JOIN volume ON matches.match_id = volume.match_id
  INNER JOIN COMMENT ON matches.match_id = comment.match_id
  ORDER BY error.error
);

с последующим:

SELECT matches_joined.*, num_duplicates
FROM matches_joined
LEFT JOIN num_duplicates ON matches_joined.match_id = num_duplicates.match_id

Однако использование LIMIT для представления имело значение:

DROP VIEW IF EXISTS `matches_joined`;
CREATE VIEW `matches_joined` AS (
  SELECT matches.match_id, source_name, target_name, transformation, STATUS , volume, error, COMMENT
  FROM matches
  INNER JOIN STATUS ON matches.match_id = status.match_id
  INNER JOIN error ON matches.match_id = error.match_id
  INNER JOIN volume ON matches.match_id = volume.match_id
  INNER JOIN COMMENT ON matches.match_id = comment.match_id
  ORDER BY error.error
  LIMIT 0, 20
);

После этого запрос был выполненна приемлемой скорости.Это уже хороший результат.Однако я чувствую, что прыгаю через обручи, чтобы заставить базу данных делать то, что я хочу, и сокращение времени, вероятно, вызвано только тем фактом, что теперь ей нужно только отсортировать 20 строк.Что если у меня будет больше строк?Есть ли другой способ заставить базу данных увидеть, что объединение в num_duplicates VIEW никак не влияет на сортировку?Могу ли я, возможно, изменить запрос, который делает просмотр немного?

Ответы [ 2 ]

1 голос
/ 06 августа 2011

Некоторые вещи, которые можно проверить, если вы еще не пробовали их. Создать представление для всех объединений с сортировкой.

DROP VIEW IF EXISTS `matches_joined`;
CREATE VIEW `matches_joined` AS (
  SELECT matches.match_id, source_name, target_name, transformation, STATUS , volume, error, COMMENT
  FROM matches
  INNER JOIN STATUS ON matches.match_id = status.match_id
  INNER JOIN error ON matches.match_id = error.match_id
  INNER JOIN volume ON matches.match_id = volume.match_id
  INNER JOIN COMMENT ON matches.match_id = comment.match_id
  ORDER BY error.error
);

Затем присоединитесь к ним с num_duplicates

SELECT matches_joined.*, num_duplicates
FROM matches_joined
LEFT JOIN num_duplicates ON matches_joined.match_id = num_duplicates.match_id

Я предполагаю, что, как указано в здесь , этот запрос будет использовать предложение order by в представлении match_joined.

Некоторая информация, которая может помочь при оптимизации.
MySQL :: MySQL 5.0 Справочное руководство :: 7.3.1.11 Оптимизация ORDER BY

0 голосов
/ 23 августа 2011

Эта проблема была более или менее решена предложением «VIEW», сделанным @ace, но в некоторых других типах запросов все еще возникали проблемы с производительностью (особенно большие OFFSET). В конце концов, большое улучшение по всем запросам этой формы было достигнуто простым форсированием поздний поиск строки . Обратите внимание, что обычно утверждается, что это необходимо только для MySQL, потому что MySQL всегда выполняет поиск в начале строки и что другие базы данных, такие как PostgreSQL, не страдают от этой проблемы. Тем не менее, обширные тесты моего приложения показали, что PostgreSQL также значительно выигрывает от этого подхода.

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