4

我有一个简单的邀请表:

CREATE TABLE `invitation` (
  `invitation_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `inviter_id` int(10) unsigned NOT NULL,
  `invitee_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`invitation_id`),
  UNIQUE KEY `invitee_inviter_idx` (`invitee_id`,`inviter_id`)
)

我想选择邀请人 70 对被邀请人 62 的邀请,反之亦然:

EXPLAIN SELECT * FROM `invitation` WHERE 
(invitee_id = 70 AND inviter_id = 62) OR (invitee_id = 62 AND inviter_id = 70)

但是这个查询是 ALL 类型的,并且不使用受邀者_邀请者_idx。请告诉我这里有什么问题?

谢谢!

==EDIT== 抱歉,我对架构有误,它还有一个字段:request_ts。这次查询计划是 ALL。

    CREATE TABLE `invitation` (
      `invitation_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `inviter_id` int(10) unsigned NOT NULL,
      `invitee_id` int(10) unsigned NOT NULL,
      `request_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
      PRIMARY KEY (`invitation_id`),
      UNIQUE KEY `invitee_inviter_idx` (`invitee_id`,`inviter_id`)
    )

这是我的解释结果:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  invitation  ALL invitee_inviter_idx \N  \N      \N  1   Using where
4

2 回答 2

10

您的选择不使用索引的原因至少有 3 个

1) 您使用了 a select *,其中包括不在索引中的项目(即invitation_id)。这意味着如果它使用了索引,那么它就必须在数据库中查找行来获取invitation_id值。如果您添加invitation_id到索引中,它将使用索引。如果你做了 a selectof just invitee_id, inviter_id,它就会使用索引。

2) 查询优化器认为只扫描表而不是扫描索引范围会更好。当优化器试图决定全表扫描或部分索引扫描时,它不会为您的确切查询执行此操作 - 它需要一个总体上运行良好的计划。一个可能会再次运行。invitee_id,inviter_id (62,70)从到扫描(70,62)可能只有 8 个索引条目,但如果从 50k 项中随机挑选,则平均距离约为 17k 项。因此,平均而言,单个查询将访问 1/3 的索引(即,将其拉入内存),然后访问该行所在的页面(参见 #1)将其拉入内存。您的行是如此之小,仅访问一项可能会拉入 680 行(8k 页乘 12 字节,用于 3 个 32 位 #'s),这是表的 1/70 - 执行 100 次查询,并且可能您已将整个索引拉入内存和整个表 - 通过扫描表花费更长的时间并使用 40% 更少的内存来保存其他表的位更有意义。在某些时候(似乎是 65k 行)它不再有意义。

3)您的问题是什么:您使用了 OR。OR 表达式不能用于在索引中查找某些内容 - 也就是说,您不能查找 62 或 70。相反,它会生成一个范围查找(62,70),然后扫描以到达(70,62)(请参阅#2 为什么这可以坏)。

您问“这里出了什么问题”-这是您使用了 OR,它无法扩展。您不仅需要避免使用 ALL 类型,还需要避免使用大类型 RANGES。

我在其他 SQL 引擎上看到过同样的问题,我使用的解决方案是 UNION ALL。

就像是

SELECT * FROM `invitation` WHERE 
    (invitee_id = 70 AND inviter_id = 62)
UNION ALL
SELECT  * FROM `invitation` WHERE
    (invitee_id = 62 AND inviter_id = 70)

这将使它作为两个查询完成并合并结果而不检查重复项。

这在内存使用上要轻得多,而且速度要快得多——只需要索引的几页和表中的两页,每次查找都需要 O(log(N))。这是因为它现在是 const 类型 - 您的目标是消除 ALL,但切换到 RANGE 几乎与仅获取两行一样糟糕。扫描整个表是 O(N),扫描索引的范围也是 O(N),因为 O(1/3*N) 是 O(N)。换句话说,它不会扩展。

于 2012-12-15T16:46:53.760 回答
3

您只需要在表中获取足够的行。MySQL 将对小表进行全表扫描,因为它足够便宜。

我的示例将 65k 行放入表中,它将使用索引。

http://sqlfiddle.com/#!2/63079/1

于 2012-12-15T17:45:05.660 回答