Я реализую следующую политику доступа: 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 (принятый участник или модератор группы / администратор)
У меня вопрос: какой самый эффективный способ перечислить Resources
(и Resource
создателя 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
, что также может быть дорогостоящим.
Есть идея получше? Этот запрос очень часто называют…