MySQL查询与子查询优化

时间:2014-11-03 17:33:02

标签: mysql optimization

我已经构建了一个具有相当典型的用户/组/权限系统的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                     |
+----+-------------+-----------------+------+----------------------------+--------------+---------+--------------------------------+------+---------------------------------+

是否可以在没有子查询的情况下重新编写查询,以便可以对其进行优化或按原样进行优化?

如果数据结构需要改变,这不是一个大问题。

2 个答案:

答案 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