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

时间:2009-02-25 15:14:02

标签: sql mysql ruby-on-rails database optimization

我正在尝试优化我的Rails应用程序中的一些数据库查询,而且我有几个让我难过。他们都在IN子句中使用WHERE,并且即使找到合适的索引,也都在进行全表扫描。

例如:

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

执行全表扫描,EXPLAIN说:

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生成的,所以我可以重新审视我的关系是如何定义的,但我认为我首先会在数据库级别开始修复。

5 个答案:

答案 0 :(得分:44)

请参阅How MySQL Uses Indexes

还要验证在向user_metrics表添加其他2000行左右的行后,MySQL是否仍然执行full table scan。在小型表中,按索引访问实际上比表扫描更昂贵(I / O方式),MySQL的优化器可能会考虑到这一点。

与我之前的帖子相反,事实证明MySQL也是using a cost-based optimizer,这是一个非常好的消息 - 也就是说,只要你运行ANALYZE至少一次当您认为数据库中的数据量是代表未来的日常使用情况时。

在处理基于成本的优化器(Oracle,Postgres等)时,您需要确保在各种表上定期运行ANALYZE,因为它们的大小增加了10-15%。 (默认情况下,Postgres会自动为您执行此操作,而其他RDBMS会将此责任留给DBA,即您。)通过统计分析,ANALYZE将帮助优化器更好地了解I / O的数量(以及其他相关资源,例如CPU,例如用于排序)将在各种候选执行计划之间进行选择时涉及。无法运行ANALYZE可能会导致非常糟糕的,有时是灾难性的计划决策(例如,由于JOIN上的错误的嵌套循环而导致的毫秒查询,有时甚至是数小时。)< / p>

如果在运行ANALYZE后性能仍不理想,那么您通常可以使用提示解决此问题,例如FORCE INDEX,而在其他情况下,您可能偶然发现了一个MySQL错误(例如older one,这可能会让您误以为您使用Rails'nested_set)。

现在,,因为您使用的是Rails应用,使用提示发出自定义查询而不是继续使用{将会很麻烦(并且无法使ActiveRecord的目的失败) {1}} - 生成的。

我之前已经提到过,在我们的Rails应用程序中,所有 ActiveRecord查询在切换到Postgres之后下降到100ms以下,而SELECT生成的一些复杂连接有时会被视为因为内部表扫描的嵌套循环,即使索引可用,MySQL 5.1也会达到15秒或更多。没有优化器是完美的,你应该知道这些选项。除了查询计划优化之外,要注意的其他潜在性能问题是锁定。但这超出了您的问题范围。

答案 1 :(得分:13)

尝试强制使用此索引:

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'

答案 2 :(得分:7)

有时MySQL不使用索引,即使有索引也是如此。发生这种情况的一种情况是,优化器估计使用索引将需要MySQL访问表中非常大比例的行。 (在这种情况下,表扫描可能会快得多,因为它需要较少的搜索。)

与IN子句匹配的行的百分比是多少?

答案 3 :(得分:3)

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

最近,我遇到了同样的问题。然后我决定使用self-join-thing来解决我的问题。 问题不在于MySQL。问题是我们。子查询的返回类型与我们的表不同。所以我们必须将子查询的类型转换为select列的类型。 以下是示例代码:

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:

  • 记录:~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:抱歉我的英语不好。我发布我的代码(生产,当然)来清除我的解决方案:D。

答案 4 :(得分:0)

如果删除where子句周围的冗余括号,它会变得更好吗?

虽然可能只是因为你只有200行左右,但它决定了表格扫描会更快。尝试使用包含更多记录的表格。