我已经构建了一个具有相当典型的用户/组/权限系统的CMS系统,其中用户可以是组的成员,并且权限可以直接应用于用户,也可以应用于用户可以成为其成员的组。
权限也可以是“通配符”(例如,应用于所有对象)或应用于由模块名称和行ID指定的特定对象。权限可以是“允许”授予访问权限,或“拒绝”可以专门阻止访问,并覆盖他们在其他地方授予的任何“允许”权限。通过创建“allow”列设置为0的行来拒绝存储在userpermission / grouppermission表中。
当前使用以下查询(并且有效)列出已被授予特定“通配符”权限的所有用户(permissionid 123)。
SELECT
`user`.*
FROM
(
SELECT
`user`.*,
`userpermission`.`allow` AS `user_allow`,
`userpermission`.`permissionid` AS `user_permissionid`,
`grouppermission`.`allow` AS `group_allow`,
`grouppermission`.`permissionid` AS `group_permissionid`
FROM
`user`
LEFT JOIN `userpermission` ON
`user`.`userid` = `userpermission`.`userid`
AND `userpermission`.`module` = '*'
AND `userpermission`.`rowid` = '*'
AND `userpermission`.`permissionid` = 18
LEFT JOIN `usergroup` ON
`user`.`userid` = `usergroup`.`userid`
LEFT JOIN `grouppermission` ON
`usergroup`.`groupid` = `grouppermission`.`groupid`
AND `grouppermission`.`module` = '*'
AND `grouppermission`.`rowid` = '*'
AND `grouppermission`.`permissionid` = 18
WHERE
(
`grouppermission`.`allow` = 1
OR
`userpermission`.`allow` = 1
)
) AS `user`
LEFT JOIN `userpermission` ON
`user`.`userid` = `userpermission`.`userid`
AND `userpermission`.`permissionid` = `user`.`user_permissionid`
AND `userpermission`.`allow` = 0
AND `userpermission`.`module` = '*'
AND `userpermission`.`rowid` = '*'
LEFT JOIN `usergroup` ON
`user`.`userid` = `usergroup`.`userid`
LEFT JOIN `grouppermission` ON
`usergroup`.`groupid` = `grouppermission`.`groupid`
AND `grouppermission`.`permissionid` = `user`.`group_permissionid`
AND `grouppermission`.`allow` = 0
AND `grouppermission`.`module` = '*'
AND `grouppermission`.`rowid` = '*'
GROUP BY `user`.`userid`
HAVING
COUNT(`userpermission`.`userpermissionid`) + COUNT(`grouppermission`.`grouppermissionid`) = 0
然而它非常慢(~0.5秒,有~3000个用户,~250个组,~10000个用户组连接,~30个权限,~150个组权限和~30个用户权限)。
根据上面的例子, permissionid只是一个permision。可能还需要检查多个权限,例如IN(18,19,20)
代替= 18
Explain提供了以下输出 - 我认为我已经索引了正确的列但是我不确定如何(或者如果可能)索引派生表:
+----+-------------+-----------------+------+----------------------------+--------------+---------+--------------------------------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------+------+----------------------------+--------------+---------+--------------------------------+------+---------------------------------+
| 1 | PRIMARY | [derived2] | ALL | NULL | NULL | NULL | NULL | 62 | Using temporary; Using filesort |
| 1 | PRIMARY | userpermission | ref | USERID,PERMISSIONID,ALLOW | USERID | 4 | user.userid | 2 | |
| 1 | PRIMARY | usergroup | ref | USERID | USERID | 4 | user.userid | 4 | |
| 1 | PRIMARY | grouppermission | ref | GROUPID,PERMISSIONID,ALLOW | PERMISSIONID | 4 | user.group_permissionid | 3 | |
| 2 | DERIVED | user | ALL | NULL | NULL | NULL | NULL | 2985 | |
| 2 | DERIVED | userpermission | ref | USERID,PERMISSIONID | PERMISSIONID | 4 | | 1 | |
| 2 | DERIVED | usergroup | ref | USERID | USERID | 4 | [database].user.userid | 4 | |
| 2 | DERIVED | grouppermission | ref | GROUPID,PERMISSIONID | PERMISSIONID | 4 | | 3 | Using where |
+----+-------------+-----------------+------+----------------------------+--------------+---------+--------------------------------+------+---------------------------------+
是否可以在没有子查询的情况下重新编写查询,以便可以对其进行优化或按原样进行优化?
如果数据结构需要改变,这不是一个大问题。
答案 0 :(得分:1)
您是left joining
,然后计算非空行并检查是否存在。我希望你从我的措辞中意识到,这比它需要的更复杂。你可以简单地重写整个查询:
SELECT
`user`.*
FROM
`user`
LEFT JOIN `userpermission` ON
`user`.`userid` = `userpermission`.`userid`
AND `userpermission`.`module` = '*'
AND `userpermission`.`rowid` = '*'
AND `userpermission`.`permissionid` = 18
LEFT JOIN `usergroup` ON `user`.`userid` = `usergroup`.`userid`
LEFT JOIN `grouppermission` ON
`usergroup`.`groupid` = `grouppermission`.`groupid`
AND `grouppermission`.`module` = '*'
AND `grouppermission`.`rowid` = '*'
AND `grouppermission`.`permissionid` = 18
WHERE
(`grouppermission`.`allow` = 1 OR `userpermission`.`allow` = 1)
AND NOT EXISTS (SELECT 1 FROM `userpermission` WHERE `user`.`userid` = `userpermission`.`userid`
AND `userpermission`.`allow` = 0
AND `userpermission`.`module` = '*'
AND `userpermission`.`rowid` = '*'
AND `userpermission`.`permissionid` = 18
)
AND NOT EXISTS (SELECT 1 FROM `grouppermission` WHERE `usergroup`.`groupid` = `grouppermission`.`groupid`
AND `grouppermission`.`allow` = 0
AND `grouppermission`.`module` = '*'
AND `grouppermission`.`rowid` = '*'
AND `grouppermission`.`permissionid` = 18
);
EXISTS()
的优点还在于,只要找到条目就会停止。如果没有,您不必获取所有行并检查它们。
编写查询的另一种方法是:
SELECT
`user`.*
FROM
`user`
LEFT JOIN `userpermission` ON
`user`.`userid` = `userpermission`.`userid`
AND `userpermission`.`module` = '*'
AND `userpermission`.`rowid` = '*'
AND `userpermission`.`permissionid` = 18
LEFT JOIN `usergroup` ON `user`.`userid` = `usergroup`.`userid`
LEFT JOIN `grouppermission` ON
`usergroup`.`groupid` = `grouppermission`.`groupid`
AND `grouppermission`.`module` = '*'
AND `grouppermission`.`rowid` = '*'
AND `grouppermission`.`permissionid` = 18
LEFT JOIN `userpermission` up ON `user`.`userid` = `up`.`userid`
AND up.`allow` = 0
AND up.`module` = '*'
AND up.`rowid` = '*'
AND up.`permissionid` = 18
LEFT JOIN grouppermission gp ON `usergroup`.`groupid` = `gp`.`groupid`
AND gp.`allow` = 0
AND gp.`module` = '*'
AND gp.`rowid` = '*'
AND gp.`permissionid` = 18
WHERE
(`grouppermission`.`allow` = 1 OR `userpermission`.`allow` = 1)
AND up.userid IS NULL
AND gp.groupid IS NULL;
试试哪一个更适合你。
答案 1 :(得分:0)
没有任何子查询的另一种方法是:
SELECT
`user`.*,
SUM(CASE WHEN `userpermission`.`allow` = 1 THEN 1 ELSE 0 END) as user_allow,
SUM(CASE WHEN `grouppermission`.`allow` = 1 THEN 1 ELSE 0 END) as group_allow,
SUM(CASE WHEN `userpermission`.`allow` = 0 THEN 1 ELSE 0 END) as user_deny,
SUM(CASE WHEN `grouppermission`.`allow` = 0 THEN 1 ELSE 0 END) as group_deny
FROM
`user`
LEFT JOIN `userpermission` ON
`user`.`userid` = `userpermission`.`userid`
AND `userpermission`.`module` = '*'
AND `userpermission`.`rowid` = '*'
AND `userpermission`.`permissionid` = 18
LEFT JOIN `usergroup` ON `user`.`userid` = `usergroup`.`userid`
LEFT JOIN `grouppermission` ON
`usergroup`.`groupid` = `grouppermission`.`groupid`
AND `grouppermission`.`module` = '*'
AND `grouppermission`.`rowid` = '*'
AND `grouppermission`.`permissionid` = 18
GROUP BY
`user`.`userid`
HAVING
(
`user_allow` > 0
OR
`group_allow` > 0
)
AND
`user_deny` = 0
AND
`group_deny` = 0