【问题标题】:mysql - OR operator not using indexmysql - OR 运算符不使用索引
【发布时间】:2012-12-03 08:42:14
【问题描述】:

我有一个简单的邀请表:

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。 请告诉我这里出了什么问题?

谢谢!

==编辑== 抱歉,我对架构有误,它还有一个字段: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

【问题讨论】:

  • 表中有多少条记录?如果表非常小,那么查询优化器可能会决定进行全面扫描,因为这样做非常便宜。
  • 我认为你只在某些情况下才能为 MyISAM 表获得它;即当您在“额外”列中看到“在阅读 const 表后注意到的不可能的地方”时
  • @Salman A,对不起,我错了,我更新了架构。
  • 我用空表输入 ALL :( 我使用的是 mysql v5.5.23
  • 用一些数据填充你的表并发布EXPLAIN的完整结果。

标签: mysql sql explain


【解决方案1】:

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

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

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)。换句话说,它无法扩展。

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 足够多的行,它会停止扫描表并对索引进行部分扫描——invitee_id 的改进但退化的值将扫描整个索引。联合都导致类型 const,而不是范围
    • 我同意联合会更快,但这不是 OP 所要求的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-29
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-10
    相关资源
    最近更新 更多