简体   繁体   English

MySQL查询与子查询优化

[英]MySQL Query with Sub-Query optimisation

I have built a CMS system with a fairly typical user/group/permission system where users can be members of groups, and permissions can be applied to either the user directly, or to groups which users can be members of. 我已经构建了一个具有相当典型的用户/组/权限系统的CMS系统,其中用户可以是组的成员,并且权限可以直接应用于用户,也可以应用于用户可以成为其成员的组。

Permissions can also be 'wildcard' (eg apply to all objects) or apply to specific objects designated by a module name and a row id. 权限也可以是“通配符”(例如,应用于所有对象)或应用于由模块名称和行ID指定的特定对象。 Permissions can with be 'Allow' which grants access, or 'Deny' which specifically prevents access and overrides any 'Allow' permissions they have been granted elsewhere. 权限可以是“允许”授予访问权限,或“拒绝”可以专门阻止访问,并覆盖他们在其他地方授予的任何“允许”权限。 Deny is stored in the userpermission/grouppermission table by creating an row with the 'allow' column set to 0. 通过创建“allow”列设置为0的行,将Deny存储在userpermission / grouppermission表中。

The following query is currently used (and works) to list all users which have been granted a specific 'wildcard' permission (permissionid 123). 当前使用以下查询(并且有效)列出已被授予特定“通配符”权限的所有用户(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

However it is very slow (~0.5 seconds, with ~3000 users, ~250 groups, ~10000 usergroup joins, ~30 permissions, ~150 grouppermissions and ~30 userpermissions). 然而它非常慢(~0.5秒,有~3000个用户,~250个组,~10000个用户组连接,~30个权限,~150个组允许和~30个用户权限)。

permissionid as per the example above is just one permision. 根据上面的例子,permissionid只是一个permision。 It may also be necessary to check multiple permissions eg IN(18,19,20) instead of = 18 可能还需要检查多个权限,例如IN(18,19,20)而不是= 18

Explain provides the following output - I think I've got the right columns indexed however I'm not sure about how (or if its possible) to index the derived table: 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                     |
+----+-------------+-----------------+------+----------------------------+--------------+---------+--------------------------------+------+---------------------------------+

Is it possible to re-write the query without the sub-query so that it can be optimised, or optimise it as-is? 是否可以在没有子查询的情况下重新编写查询,以便可以对其进行优化或按原样进行优化?

If the data structure needs changing that isn't a huge issue. 如果数据结构需要改变那不是一个大问题。

You're left joining and afterwards you count the non-null rows and check if there exist none. 您将left joining ,然后计算非空行并检查是否存在。 I hope you realize from my wording, that this is more complicated than it needs to be. 我希望你从我的措辞中意识到,这比它需要的更复杂。 You can simply rewrite the whole query like this: 你可以简单地重写整个查询,如下所示:

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
                       );

What's also great about EXISTS() , is, that it stops as soon as an entry was found. EXISTS()还在于,只要找到条目就会停止。 You don't have to get all rows and check afterwards if there was none. 如果没有,您不必获取所有行并在之后进行检查。

Another way to write the query would be this: 编写查询的另一种方法是:

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;

Have a try which one works better for you. 试试哪一个更适合你。

Another way to do this without any sub-queries is: 没有任何子查询的另一种方法是:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM