如何通过简单的 AND + OR 查询让 mysql 使用索引?
Posted
技术标签:
【中文标题】如何通过简单的 AND + OR 查询让 mysql 使用索引?【英文标题】:How can I get mysql to use indexes with a simple AND + OR query? 【发布时间】:2012-07-11 17:41:52 【问题描述】:这是使用 mysql 5.5。 我似乎无法说服 MySQL 为这些查询使用索引,而且它们在 110 万行的表上运行需要 2-10 秒。
表:
CREATE TABLE `notifiable_events` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(255) NOT NULL,
`trigger_profile_id` int(10) unsigned DEFAULT NULL,
`model1` varchar(25) NOT NULL,
`model1_id` varchar(36) NOT NULL,
`model2` varchar(25) NOT NULL DEFAULT '',
`model2_id` varchar(36) NOT NULL DEFAULT '',
`event_data` text,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
`deleted` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `key` (`key`),
KEY `notifiable_events__trigger_profile` (`trigger_profile_id`),
KEY `deleted` (`deleted`),
KEY `noti_evnts__m2` (`model2`),
KEY `noti_evnts__m1` (`model1`),
CONSTRAINT `notifiable_events__trigger_profile` FOREIGN KEY (`trigger_profile_id`) REFERENCES `profiles` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1177918 DEFAULT CHARSET=utf8
查询:
SELECT *
FROM notifiable_events
WHERE (`model1` = 'page' AND `model1_id` = '54321')
OR (`model2` = 'page' AND `model2_id` = '12345');
解释:
mysql> EXPLAIN EXTENDED SELECT * FROM notifiable_events WHERE (`model1` = 'page' AND `model1_id` = '922645') OR (`model2` = 'page' AND `model2_id` = '922645')\G ****************************** 1. 行 ************************ ******* 编号:1 选择类型:简单 表:notifiable_events 类型:全部 可能键:noti_evnts__m2,noti_evnts__m1,noti_evnts__m1_m2 键:空 key_len:空 参考:空 行数:1033088 过滤:100.00 额外:使用 where 1 行,1 个警告(0.00 秒) mysql> EXPLAIN EXTENDED SELECT * FROM notifiable_events WHERE (`model1` = 'page' AND `model1_id` = '922645') OR (`model1` = 'page' AND `model1_id` = '922645')\G ****************************** 1. 行 ************************ ******* 编号:1 选择类型:简单 表:notifiable_events 类型:参考 可能键:noti_evnts__m1,noti_evnts__m1_m2 键:noti_evnts__m1 关键长度:77 参考:常量 行数:1 过滤:100.00 额外:使用 where 1 行,1 个警告(0.00 秒) mysql> EXPLAIN EXTENDED SELECT * FROM notifiable_events WHERE (`model2` = 'page' AND `model2_id` = '922645') OR (`model2` = 'page' AND `model2_id` = '922645')\G ****************************** 1. 行 ************************ ******* 编号:1 选择类型:简单 表:notifiable_events 类型:参考 可能的键:noti_evnts__m2 键:noti_evnts__m2 关键长度:77 参考:常量 行数:428920 过滤:100.00 额外:使用 where 1 行,1 个警告(0.00 秒)您可以看到,如果我只使用 model1 或只使用 model2,那么它将使用索引,但是,一旦我尝试将它们一起使用,它就会完全放弃索引并进行全表扫描。 我已经尝试过 FORCE INDEX 并且我尝试了多键索引的组合(我为这个表留下了一个作为示例)。我也尝试过重新排列查询中元素的顺序,但这似乎也没有任何效果。
更新: 我忘了提到我已经尝试过 ANALYZE 和 OPTIMIZE (每次多次,没有变化)。我还已经尝试了 *_id 上的索引(基数非常糟糕,这些列大多是唯一条目)和一个多索引,查询中使用了所有 4 列。在这两种情况下也没有改进或使用索引。
似乎使用索引来限制这里检查的行应该很容易,所以我希望我只是遗漏了一些东西。
【问题讨论】:
如果x_id
列似乎应该被索引(而不是)?无论如何,如果表的统计信息更新了怎么办?
【参考方案1】:
or
子句有时会妨碍查询优化。
你可以试试联合,像这样。它可能会重新激活索引。
SELECT *
FROM notifiable_events
WHERE `model1` = 'page' AND `model1_id` = '54321'
UNION
SELECT *
FROM notifiable_events
WHERE `model2` = 'page' AND `model2_id` = '12345'
编辑,回答评论中的问题
如果您尝试使用这种选择方案更新记录,您可以试试这个:
UPDATE notifiable_events
SET what=ever,
what=ever,
what=else
WHERE id IN (
SELECT id
FROM notifiable_events
WHERE `model1` = 'page' AND `model1_id` = '54321'
UNION
SELECT id
FROM notifiable_events
WHERE `model2` = 'page' AND `model2_id` = '12345'
)
(请注意,当您使用 *** 来解释您实际尝试做的事情时,它会很有帮助。如果可以的话,当然可以将一个复杂的问题简化为一个更简单的问题,但要说你'在实际执行 UPDATE 时重新执行 SELECT 是,嗯,过于简单化了。)
【讨论】:
这将需要一些 DISTINCT-ish 以使结果相同..(或者可以多次选择相同的记录) 文档说:UNION 的默认行为是从结果中删除重复的行。可选的 DISTINCT 关键字除了默认值之外没有任何作用,因为它还指定重复行删除。使用可选的 ALL 关键字,不会删除重复行,结果包括所有 SELECT 语句中的所有匹配行。" @pstunion
排序并删除重复项。 union all
保持所有行不变(按编码顺序)
在这种情况下,UNION 将完全模仿“OR”
是否可以将 union 与 UPDATE 语句一起使用?大多数这些查询实际上是更新,但由于 MySQL 不支持在更新时解释我发布了 SELECT 的结果(即使我们实际上没有这样的选择)以上是关于如何通过简单的 AND + OR 查询让 mysql 使用索引?的主要内容,如果未能解决你的问题,请参考以下文章