我有这张表:
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过滤,那么它只需要搜索几十行。
索引(或查询?)
有什么问题谢谢!
答案 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-by
和Range 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
现在的查询将汇总1st
,2nd
等所有月份和年份的所有值。
予。即对于第一组,它会将Jan 1st, 2000
,Feb 1st, 2000
等所有值相加。
然后它将返回任意随机值MONTH
,任意随机值TO_DAYS
和任意随机每组的di_abt
值。
你的病情现在只有一个月,所以现在好了,但如果你的病情会持续数月(更不用说几年了),他们的查询会产生意想不到的结果。
你真的想按日期分组吗?
答案 1 :(得分:0)
您正在扫描索引的第一部分 - 因此它无法使用索引的后续部分。
改进此方法的方法是创建另一个索引,其中的字段顺序不同,这更有利于此特定查询。
如果你的索引是di_sid,di_type,di_date那么它可能会更好。