2009-12-16 45 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