2009-12-16 61 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) 

授予访问权的条件是这种方式实现的: *的UserResource的创造者(user_id = <his id>,无论ACCESS_TYPE是) *或者资源是公开可用的:Resource.access_id = 3 *或者资源在组中共享并且用户被接受为该组的成员: ** Resource.access_id = 2(在组中共享) ** Ressource.group_id匹配User_Groupgroup_id **这User_Groupuser_id的用户的ID **匹配这个User_Groupmembership_type是1,2或3(接受成员或组慢化剂/管理员)

我的问题是:什么是列出Resources(和Resource创建者的nickname)最有效的方式,当我们有他的ID时,用户可以访问它。并按资源的created时间戳排序。

我最好的尝试,到目前为止是使用UNION,允许同时使用by_userby_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并在UNION的行上以type: ALL/Using filesort结束ORDER BY。但是,如果我想排序和限制,我猜可能会变得很沉重,因为在元组上没有索引。

另一种可能是为GroupsUser子查询一个简单的查询是:

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告诉我,它不会使用索引,并在Resource执行选择type: ALL/Using where; using filesort表,女巫是我想要避免的。如果我尝试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; 

任何时候,您的主要WHERE子句是OR的序列,理想的EXPLAIN结果将是某种类型的index_merge的。但是,您认为sort_union是其中速度最慢的。如果你阅读了index_merge,特别是关于每种合并类型的子部分,就很清楚你的WHERE子句的OR部分需要引用一个键的所有部分,否则你将得到一个sort_union。所以,你应该得到一个更好地解释结果,如果你有资源索引:

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

感谢您花时间阅读和回答。当我开始工作时,我会运行一些测试。 – 2010-01-04 17:13:33