我有一个简单的邀请表:
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
答案 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)