Производительность MySQL запросов по двум индексам + сортировка - PullRequest
0 голосов
/ 16 декабря 2009

Я реализую следующую политику доступа: User может получить доступ к Resource, если он его создал, принадлежит членам группы Resource или если ресурс общедоступен.

Вот моя структура БД (таблицы MyISAM):

User (1K-10K Users)
  id
  nickame
  …
  index user_name(id, nickname)
Group (1K)
  id
  …
Resource (10K-100K)
  id
  user_id
  access_type (int)
  group_id
  created (int/timestamp)
  …
  indexes by_user(user_id, created), by_group(access_type, group_id, created), public(access_type, created)
User_Group (5K-20K)
  user_id
  group_id
  membership_type (int)
  index user_group(user_id, membership_type, group_id)

Условия предоставления доступа реализованы следующим образом: * User является создателем Resource (user_id = <his id>, независимо от типа access_type) * Или ресурс общедоступен: Resource.access_id = 3 * Или ресурс является общим в группе, и пользователь принимается как «участник» этой группы: ** Resource.access_id = 2 (общий доступ в группе) ** Ressource.group_id соответствует User_Group group_id ** этот User_Group 'user_id соответствует идентификатору пользователя ** это User_Group membership_type - 1, 2 или 3 (принятый участник или модератор группы / администратор)

У меня вопрос: какой самый эффективный способ перечислить ResourcesResource создателя nickname), которые доступны Пользователю, когда у нас есть его ID. И заказывается по метке времени created Ресурса.

Моя лучшая попытка на данный момент - использовать UNION, который позволяет использовать как индексы by_user, так и by_group, но впоследствии может возникнуть проблема при сортировке:

SELECT SQL_NO_CACHE
  a.*, user.nickname
FROM (
  ( SELECT resource.* FROM resource WHERE user_id = '000000-0000-0000-0000-000000000000' )
UNION
  ( SELECT resource.* FROM resource WHERE access_type = 3 )
UNION
  ( SELECT resource.* FROM resource
   INNER JOIN user_group ON (access_type = 2 AND user_group.group_id = resource.group_id)
   WHERE user_group.user_id = '000000-0000-0000-0000-000000000000'
    AND user_group.type IN (1, 2, 3)
  )
) a
LEFT JOIN user ON (user.id = a.user_id)
ORDER BY created DESC;

Вывод EXPLAIN говорит мне, что он использует индексы для SELECT s и заканчивается type: ALL / Using filesort для строк ORDER BY на UNION. Но если я захочу упорядочить и ограничить, это может стать тяжелым, я думаю, так как нет индекса UNION -еданных кортежей.

Другая возможность - более простой запрос с подзапросом для Groups, в котором находится User:

SELECT SQL_NO_CACHE
  resource.*, user.nickname
FROM resource
LEFT JOIN user ON (user.id = resource.user_id) 
WHERE user_id = '000000-0000-0000-0000-000000000000'
  OR access_type = 3
  OR (access_type = 2 AND group_id IN
   ( SELECT group_id FROM user_group USE INDEX (`user_group`)
   WHERE user_id = '000000-0000-0000-0000-000000000000' AND type IN (1, 2, 3)
   )
  )
ORDER BY created DESC;

Но здесь EXPLAIN говорит мне, что он не будет использовать индекс и выполняет выбор type: ALL / Using where; using filesort в таблице Resource, ведь я стараюсь избегать этого. Если я пытаюсь FORCE INDEX(by_user, by_group), он начинается с объединения обоих индексов type: index_merge / Using sort_union(by_user,by_group); Using where; Using filesort, что также может быть дорогостоящим.

Есть идея получше? Этот запрос очень часто называют…

1 Ответ

1 голос
/ 25 декабря 2009

Я думаю, что второе объединение будет лучше использовать индексы здесь по сравнению с предложенным вами подзапросом (просто нужно добавить DISTINCT, чтобы избежать дублирования).

  SELECT SQL_NO_CACHE DISTINCT
         resource.*, user.nickname
    FROM resource
LEFT JOIN user ON user.id = resource.user_id
LEFT JOIN user_group ON user_group.user_id = user.id AND user_group.type IN (1, 2, 3)
    WHERE user_id = '000000-0000-0000-0000-000000000000'
       OR access_type = 3
      OR (access_type = 2 AND group_id IS NOT NULL)
ORDER BY created DESC;

Каждый раз, когда ваше основное предложение WHERE представляет собой последовательность OR, идеальным результатом EXPLAIN будет некоторый тип index_merge. Тем не менее, вы правы, что sort_union является самым медленным из них. Если вы прочитаете index_merge , особенно его части о каждом типе слияния, станет ясно, что части OR вашего предложения WHERE должны ссылаться на все части ключа, в противном случае вы получите sort_union , Таким образом, вы должны получить лучший результат EXPLAIN, если у вас были индексы ресурсов:

Resource (10K-100K)
id
user_id
access_type (int)
group_id
created (int/timestamp)
…
indexes 
    just_user(user_id),
    by_user(user_id, created),
    just_access(access_type),
    just_access_group(access_type, group_id),
    by_group(access_type, group_id, created),
    public(access_type, created)

Я признаю, что это противоречит тому, что вы получаете бесплатные префиксы в качестве индексов бесплатно, когда вы делаете многокомпонентные ключи, но кажется, что алгоритм index_merge не может (пока?) Использовать эти индексы частичных префиксов.

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