MySQL不使用WHERE IN子句中的索引?

我试图优化我的Rails应用程序中的一些数据库查询,我有几个让我难住。 他们都在WHERE子句中使用了一个IN,并且即使恰当的索引似乎已经到位,他们也都在进行全表扫描。

例如:

SELECT `user_metrics`.* FROM `user_metrics` WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

执行全表扫描和说明:

 select_type: simple type: all extra: using where possible_keys: index_user_metrics_on_user_id (which is an index on the user_id column) key: (none) key_length: (none) ref: (none) rows: 208 

在使用IN语句时是不是使用了索引,还是我需要做一些不同的事情? 这里的查询是由Rails生成的,所以我可以重新审视我的关系是如何定义的,但是我想我会先从数据库级别的潜在修复开始。

看MySQL如何使用索引 。

同时validation在向user_metrics表添加额外2000行左右的行之后,MySQL是否仍然执行全表扫描 。 在小表中,索引访问实际上比表扫描更昂贵(I / O方式),MySQL的优化器可能会考虑到这一点。

与我以前的文章相反 ,事实certificate,MySQL也使用了基于成本的优化器 ,这是一个非常好的消息 – 也就是说,如果您相信数据库中的数据量是有代表性的 ,那么您至less要运行一次ANALYZE未来的日常使用。

在处理基于成本的优化器(Oracle,Postgres等)时,您需要确保在各种表中定期运行ANALYZE ,因为它们的大小增加了10-15%以上。 (默认情况下,Postgres会自动为你做这件事,而其他的RDBMSs会把这个责任交给一个DBA,也就是你)。通过统计分析, ANALYZE将帮助优化器更好地理解I / O(和其他关联在各种候选执行计划之间进行select时将涉及例如用于分类所需的诸如CPU的资源)。 未能运行ANALYZE可能会导致非常糟糕的,有时甚至是灾难性的计划决策(例如,由于JOIN嵌套循环不好 ,毫秒级查询需要花费数小时)。

如果运行ANALYZE后性能仍然不理想,那么通常可以通过使用提示(例如FORCE INDEX来解决问题,而在其他情况下,您可能会偶然发现一个MySQL错误(例如, 较老的可能已经被咬你是使用Rails的nested_set )。

现在, 由于您处于Rails应用程序中 ,因此使用提示来发出自定义查询(而不是继续使用ActiveRecord生成的查询)会很麻烦(并且失败了ActiveRecord的用途)。

我曾经提到,在我们的Rails应用程序中, 所有的 SELECT查询在切换到Postgres后下降到了100ms以下,而由ActiveRecord生成的一些复杂的连接有时会因为使用内部表扫描的嵌套循环而在MySQL 5.1中占用多达15s或更多的时间,指数可用时。 没有优化器是完美的,你应该知道的选项。 其他潜在的性能问题,除了查询计划优化外,还有locking。 虽然这不在你的问题范围之内。

试着强制这个索引:

 SELECT `user_metrics`.* FROM `user_metrics` FORCE INDEX (index_user_metrics_on_user_id) WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

我只是检查,它确实使用完全相同的查询索引:

 EXPLAIN EXTENDED SELECT * FROM tests WHERE (test IN ('test 1', 'test 2', 'test 3', 'test 4', 'test 5', 'test 6', 'test 7', 'test 8', 'test 9')) 1, 'SIMPLE', 'tests', 'range', 'ix_test', 'ix_test', '602', '', 9, 100.00, 'Using where' 

有时MySQL不使用索引,即使有索引。 出现这种情况的一种情况是,优化程序估计使用索引需要MySQL访问表中非常大部分的行。 (在这种情况下,表扫描可能会快得多,因为它需要更less的search。)

有多less行与您的IN子句匹配?

我知道我迟到了。 但希望我能帮助有类似问题的其他人。

最近,我有同样的问题。 然后,我决定使用自我join的东西来解决我的问题。 问题不是MySQL。 问题是我们。 子查询的返回types与我们的表不同。 所以我们必须将types的子查询转换为select列的types。 以下是示例代码:

 select `user_metrics`.* from `user_metrics` um join (select `user_metrics`.`user_id` in (N, N, N, N) ) as temp on um.`user_id` = temp.`user_id` 

或者我自己的代码:

旧:(不使用索引:〜4s)

 SELECT `jxm_character`.* FROM jxm_character WHERE information_date IN (SELECT DISTINCT (information_date) FROM jxm_character WHERE information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) AND `jxm_character`.`ranking_type` = 1 AND `jxm_character`.`character_id` = 3146089; 

新:(使用指数:〜0.02s)

 SELECT * FROM jxm_character jc JOIN (SELECT DISTINCT (information_date) FROM jxm_character WHERE information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) AS temp ON jc.information_date = STR_TO_DATE(temp.information_date, '%Y-%m-%d') AND jc.ranking_type = 1 AND jc.character_id = 3146089; 

jxm_character:

  • logging:〜3.5M
  • PK:jxm_character(information_date,ranking_type,character_id)

SHOW VARIABLES LIKE '%version%';

 'protocol_version', '10' 'version', '5.1.69-log' 'version_comment', 'Source distribution' 

最后一点:确保你了解MySQL索引最左边的规则。

P / S:对不起我的英文不好。 我发布我的代码(当然是生产)来清除我的解决scheme:D。

如果删除where子句中的冗余括号,它会更好吗?

虽然它可能只是因为你只有200行左右,所以决定一个表扫描会更快。 尝试使用一个包含更多logging的表格。