为什么这个索引不起作用(Mysql)

时间:2009-10-15 15:44:56

标签: sql mysql

我有这张表:

CREATE TABLE  `maindb`.`daily_info` (
  `di_date` date NOT NULL,
  `di_sid` int(10) unsigned NOT NULL default '0',
  `di_type` int(10) unsigned NOT NULL default '0',
  `di_name` varchar(20) NOT NULL default '',
  `di_num` int(10) unsigned NOT NULL default '0',
  `di_abt` varchar(1) NOT NULL default 'a',
  PRIMARY KEY  (`di_date`,`di_sid`,`di_type`,`di_name`,`di_abt`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

当我使用此查询时:

explain
SELECT  MONTH(di_date) as label1, DAYOFMONTH(di_date) as label2, sum(di_num) as count , di_abt as abt
FROM   `daily_info`
WHERE  di_sid=6
       AND di_type = 4
       AND di_name='clk-1'
       AND di_date > '2009-10-01' AND di_date < '2009-10-16'
GROUP BY
       DAYOFMONTH(di_date)
ORDER BY
       TO_DAYS(di_date) DESC

我明白了:

1, 'SIMPLE', 'daily_info', 'range', 'PRIMARY', 'PRIMARY', '3', '', 2500, 'Using where; Using temporary; Using filesort'

实际上如果密钥有效并且查询将被di_date,di_sid和di_type过滤,那么它只需要搜索几十行。

索引(或查询?)

有什么问题

谢谢!

2 个答案:

答案 0 :(得分:4)

您在第一个索引列上使用范围条件,这可能会导致在其他列上进行过滤。

此索引中没有单个连续范围包含那些且只包含那些满足条件的记录。

MySQL无法执行SKIP SCAN跳过di_date的不同值。这就是为什么它做得最好:使用range访问权限对di_date进行过滤,并使用WHERE过滤所有其他字段。

重新创建索引(最佳决策):

PRIMARY KEY  (`di_sid`,`di_type`,`di_name`,`di_date`,`di_abt`)

或者,如果您无法重新创建索引,则可以模拟SKIP SCAN

SELECT  MONTH(di.di_date) as label1, DAYOFMONTH(di.di_date) as label2, sum(di.di_num) as count , di.di_abt as abt
FROM    (
        SELECT  DISTINCT di_date
        FROM    daily_info
        WHERE   di_date > '2009-10-01' AND di_date < '2009-10-16'
        ) do
JOIN    daily_info di
ON      di.di_date <= do.di_date
        AND di.di_date>= do.di_date
        AND di_sid = 6
        AND di_type = 4
        AND di_name = 'clk-1'
GROUP BY
        DAYOFMONTH(di.di_date)
ORDER BY
        TO_DAYS(di.di_date) DESC

确保计划中包含Using index for group-byRange checked for each record

这个条件:

di.date <= do.date
AND di.date >= do.date
使用

代替简单di.date = do.date来强制进行范围检查。

有关模拟SKIP SCAN的详细说明,请参阅我的博客中的这篇文章:

<强>更新

后一个查询实际上使用了等值连接,MySQL在没有技巧的情况下优化它。

上述技巧仅适用于远程查询,即。即当最里面的循环应该使用range访问权限,而不是ref访问权限。

如果您必须执行di_name <= 'clk-1'

之类的操作,那将非常有用

此查询应该可以正常工作:

SELECT  MONTH(di.di_date) as label1, DAYOFMONTH(di.di_date) as label2, sum(di.di_num) as count , di.di_abt as abt
FROM    (
        SELECT  DISTINCT di_date
        FROM    daily_info
        WHERE   di_date > '2009-10-01' AND di_date < '2009-10-16'
        ) do
JOIN    daily_info di
ON      di.di_date = do.di_date
        AND di_sid = 6
        AND di_type = 4
        AND di_name = 'clk-1'
GROUP BY
        DAYOFMONTH(di.di_date)
ORDER BY
        TO_DAYS(di.di_date) DESC

通过di

确保ref在整个子项上使用key_len = 33访问权限

更新2

在您的查询中,您使用的是GROUP BY

中的这些表达式
MONTH(di_date)
TO_DAYS(di_date)
di_abt

现在的查询将汇总1st2nd等所有月份和年份的所有值。

予。即对于第一组,它会将Jan 1st, 2000Feb 1st, 2000等所有值相加。

然后它将返回任意随机值MONTH任意随机值TO_DAYS任意随机每组的di_abt值。

你的病情现在只有一个月,所以现在好了,但如果你的病情会持续数月(更不用说几年了),他们的查询会产生意想不到的结果。

你真的想按日期分组吗?

答案 1 :(得分:0)

您正在扫描索引的第一部分 - 因此它无法使用索引的后续部分。

改进此方法的方法是创建另一个索引,其中的字段顺序不同,这更有利于此特定查询。

如果你的索引是di_sid,di_type,di_date那么它可能会更好。