mysql - OR运算符不使用索引

时间:2012-12-15 16:37:03

标签: mysql sql explain

我有一个简单的邀请表:

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,并且不使用invitee_inviter_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`)
    )

这是我的exlain结果:

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

2 个答案:

答案 0 :(得分:6)

至少有3个理由说明您的选择未使用索引

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

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

3)你的问题是什么:你使用过OR。 OR表达式不能用于查找索引中的某些内容 - 也就是说,您无法查找62或70.相反,它会生成查找(62,70)的范围,然后扫描以转到{{ 1}}(见#2为什么这可能不好)。

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

我已经看到了与其他SQL引擎相同的问题,我使用的解决方案是UNION ALL。

这样的东西
(70,62)

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

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

答案 1 :(得分:3)

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

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

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