2

当我不能使用索引来满足行的排序时,如何使具有小 LIMIT(即一次 20 行)的 ORDER BY 子句快速返回?

假设我想从表“节点”(下面简化)中检索一定数量的标题。顺便说一句,我正在使用 MySQL。

node_ID INT(11) NOT NULL auto_increment,
node_title VARCHAR(127) NOT NULL,
node_lastupdated INT(11) NOT NULL,
node_created INT(11) NOT NULL

但我需要将返回的行限制为仅特定用户有权访问的行。许多用户可以访问大量节点。我在一个大查找表中预先计算了这些信息(试图让事情变得更容易),其中主键涵盖两列,并且行的存在意味着用户组可以访问该节点:

viewpermission_nodeID INT(11) NOT NULL,
viewpermission_usergroupID INT(11) NOT NULL

因此,我的查询包含类似

FROM
  node
  INNER JOIN viewpermission ON
    viewpermission_nodeID=node_ID
    AND viewpermission_usergroupID IN (<...usergroups of current user...>)

...而且我还使用 GROUP BY 或 DISTINCT 以便即使两个用户的“用户组”都可以访问该节点,一个节点也只会返回一次。

我的问题是,对于按创建或上次更新日期对结果进行排序的 ORDER BY 子句似乎无法使用索引,因为返回的行取决于另一个 viewpermission 表中的值。

因此 MySQL 需要找到所有符合条件的行,然后自己对它们进行排序。如果特定用户有一百万行,并且我们想查看最新的 100 行或按上次更新排序时的 100-200 行,则数据库需要确定用户可以看到哪些一百万行,排序这整个结果集本身,在它可以返回那 100 行之前,对吧?

有没有什么创造性的方法来解决这个问题?我一直在思考:

  • 以某种方式将日期添加到查看权限查找表中,以便我可以建立一个包含日期和权限的索引。我猜这是一种可能。

编辑:简化问题

也许我可以通过这样重写来简化问题:

有什么方法可以重写此查询或为以下内容创建索引,以便可以使用索引进行排序(不仅仅是选择行)?

SELECT nodeid
FROM lookup
WHERE
  usergroup IN (2, 3)
GROUP BY
  nodeid

(usergroup) 上的索引允许索引满足 WHERE 部分,但 GROUP BY 强制对这些行使用临时表和文件排序。(nodeid) 上的索引对我没有任何作用,因为 WHERE 子句需要一个以用户组作为第一列的索引。(usergroup, nodeid) 上的索引强制使用临时表和文件排序,因为 GROUP BY 不是可以变化的索引的第一列。

有什么解决办法吗?

4

4 回答 4

3

我可以回答我自己的问题吗?

我相信我发现,做我所描述的事情的唯一方法是让我的查找表为每个可能的用户组组合包含一个人可能希望成为其中成员的行。

要选择一个简化的示例,而不是这样做:

SELECT id FROM ids WHERE groups IN(1,2) ORDER BY id

如果您需要使用索引来选择行并对其进行排序,则必须抽象该 IN(1,2) 以便它是常量而不是范围,即:

SELECT id FROM ids WHERE grouplist='1,2' ORDER BY id

当然,除了使用字符串 '1,2' 之外,您还可以在其中有一个外键,等等。关键是,您不仅要为每个组,而且要为多个组的每个组合设置一行。

所以,有我的答案。

无论如何,对于我的应用程序,我觉得为每个节点维护所有可能的用户组组合的查找是不值得的。出于我的目的,我预测大多数节点对大多数用户都是可见的,所以我觉得简单地让 GROUP BY 使用索引是可以接受的,因为过滤并不那么需要它。

换句话说,我对原始查询采用的方法可能类似于:

SELECT
    <fields>
FROM
  node
  INNER JOIN viewpermission ON
    viewpermission_nodeID=node_ID
    AND viewpermission_usergroupID IN (<...usergroups of current user...>)
  FORCE INDEX(node_created_and_node_ID)
GROUP BY
  node_created, node_ID

如果 GROUP BY 从索引的最左侧列开始并且它位于要处理的第一个非常量非系统表中,则可以使用索引。然后连接处理整个列表(已经排序),只有那些对当前用户不可见的(将是一小部分)被 INNER JOIN 删除。

于 2009-02-26T08:14:26.560 回答
0

将您要订购的值复制到查看权限表并将其添加到您的索引中。

您可以使用触发器来维护另一个表中的该值。

于 2009-02-26T03:01:17.870 回答
0
select * from
(
select *
FROM  node  
INNER JOIN viewpermission 
ON    viewpermission_nodeID=node_ID    
AND viewpermission_usergroupID IN (<...usergroups of current user...>)
) a
order by a.node_lastupdated desc

内部查询为您提供了过滤后的子集,据我所知,它比整个集合要小得多。只有较小的需要排序。

于 2009-02-26T03:05:33.743 回答
0

当你在同一个查询中使用GROUP BY和时,MySQL 会出现问题。ORDER BY这会导致文件排序,这可能是对性能的最大损失。

您可以通过使用非相关子查询而DISTINCT不是.GROUP BYJOIN

SELECT * FROM node
WHERE node_id IN (
  SELECT viewpermission_nodeID
  FROM viewpermission
  WHERE viewpermissiong_usergroupID IN ( <...usergroups...> )
)
ORDER BY node_lastupdated DESC
LIMIT 100;

无需DISTINCT对子查询进行排序或执行 a ,因为IN (1, 1, 2, 3)IN (1, 3, 2).

请注意,MySQL 在给定查询中每个表只能使用一个索引,因此它会尝试在 index onnode_id和 index on之间做出最佳选择node_lastupdated。它不能同时使用两者,即使您制作了复合索引,在这种情况下也无济于事。

请记住使用 分析不同的解决方案EXPLAIN

于 2009-02-26T03:44:37.503 回答