2009-12-16 3 views
0

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

Вот моя структура БД (таблицы 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) 

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

Мой вопрос: что такое наиболее эффективный способ перечислить ResourcesResource создателя nickname), которые доступны для пользователя, когда у нас есть его идентификатор. И упорядочен по метке времени 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 с и заканчивается на type: ALL/Using filesort для ORDER BY на UNION рядами. Но если я хочу заказать и ограничить, это может стать тяжелым, я думаю, так как нет индекса на UNION -ed кортежей.

Другая возможность более простой запрос с подзапросом для 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

Я думаю, что второе соединение будет лучше использовать индексы здесь вместо предложенного вами подзапроса (просто нужно добавить 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; 

Каждый раз, когда ваш основной ИНЕК последовательность 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 не могут (пока?) использовать те частичные префиксные индексы.

+0

Спасибо, что нашли время, чтобы прочитать и ответить. Когда я начну работать над этим, я проведу несколько тестов. –