Hi,
I'm implementing the following access policy: a User can access a Resource if he created it, belongs to the Resource's group members or if the resource is publicly available.
Here is my DB structure (tables are 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 conditions to grant access are implemented this way:
* The User is the creator of the Resource (user_id = <his id>, whatever access_type is)
* Or the ressource is publicly available: Resource.access_id = 3
* Or the ressource is shared in a group and the User is accepted as "member" of this group:
** Resource.access_id = 2 (shared in a group)
** Ressource.group_id matches a User_Group's group_id
** this User_Group's user_id matches the User's id
** this User_Group's membership_type is 1, 2 or 3 (accepted member or group moderator/admin)
My question is: what is the most efficient way to list the Resources (and the Resource creator's nickname) that are accessible to the User when we have his ID. And ordered by the Resource's created timestamp.
My best attempt so far is to use an UNION that allows using both by_user and by_group indexes, but might struggle in the sorting afterwards:
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;
The EXPLAIN output tells me it uses the indexes for both SELECTs and finishes with a type: ALL / Using filesort for the ORDER BY on UNION-ed rows. But if I want to order and limit, this can get heavy, I guess, since there is no index on UNION-ed tuples.
The other possibility is a simpler query with a subquery for the Groups the User is in:
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;
But here the EXPLAIN tells me it won't use the index and performs a selection type: ALL / Using where; using filesort in the Resource table, witch is what I'm trying to avoid. If I try to FORCE INDEX(by_user, by_group), it is starts by merging both indexes type: index_merge / Using sort_union(by_user,by_group); Using where; Using filesort, which can be costly too.
Any better idea ? This request may be very frequently called…