MySQL中的一个1000万行表(单表)中的SELECT查询非常慢

时间:2014-12-09 14:38:36

标签: mysql performance select optimization

我的查询存在性能问题。 这是表模式:

CREATE TABLE `file_info` (
  `FILE_NAME` varchar(255) DEFAULT '',
  `START_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `END_TIME` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `FILE_SIZE` int(10) NOT NULL DEFAULT '0',
  `LOG_SERVER_NAME` varchar(255) NOT NULL DEFAULT '',
  `PHASE` varchar(255) NOT NULL DEFAULT '',
  `APPLICATION` varchar(255) NOT NULL DEFAULT '',
  `TYPE` varchar(255) NOT NULL DEFAULT '',
  `FULLPATH` varchar(255) NOT NULL DEFAULT '',
  `COMPRESSED` tinyint(1) NOT NULL DEFAULT '0',
  `CLOSED` tinyint(1) NOT NULL DEFAULT '0',
  `ARCHIVED_PATH` varchar(255) NOT NULL DEFAULT '',
  `FILE_TYPE` varchar(45) NOT NULL DEFAULT '',
  PRIMARY KEY (`LOG_SERVER_NAME`,`FULLPATH`),
  UNIQUE KEY `uk_file_info` (`LOG_SERVER_NAME`,`FULLPATH`,`APPLICATION`) USING BTREE,
  KEY `IDX_STARTTIME` (`START_TIME`),
  KEY `IDX_ENDTIME` (`END_TIME`),
  KEY `IDX_PHASE` (`PHASE`),
  KEY `IDX_APLICATION` (`APPLICATION`),
  KEY `IDX_LOGSERVERNAME` (`LOG_SERVER_NAME`),
  KEY `IDX_FULLPATH` (`FULLPATH`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

这是我的问题:

SELECT * FROM FILE_INFO 
WHERE PHASE ='DEV' 
AND APPLICATION ='SIALT'
AND ((START_TIME <'2014-11-11 08:17:00' AND END_TIME >'2014-11-11 08:17:00') 
   OR (START_TIME <'2014-11-11 08:22:00' AND END_TIME >'2014-11-11 08:22:00') 
   OR (START_TIME >'2014-11-11 08:17:00' AND END_TIME <'2014-11-11 08:22:00'))

查询任务的时间很少。有时超过30秒。

我已将索引放在我要过滤的字段上。 我正在使用MyISAM,因为我知道如果DB没有外键会更好。

所以我正在寻找新的想法来改进我的查询。它今天几乎无法使用。

添加自动增量键会有帮助吗?即使我不按ID过滤? 从MyISAM改为InnoDB?

修改

解释给出

id  1
select_type SIMPLE
table   FILE_INFO
type    ref
possible_keys   IDX_STARTTIME,IDX_ENDTIME,IDX_PHASE,IDX_APLICATION
key IDX_APLICATION
key_len 257
ref const
rows    756718
Extra   Using index condition; Using where

我会尝试其他建议并更新我的帖子。

感谢您的提示。

罗曼。

3 个答案:

答案 0 :(得分:1)

您应该使用RANGE BASED分区,根据日期创建分区,可能是一个月或一周,这将提升性能。

答案 1 :(得分:1)

您应该将VARCHAR列的大小减小到可以的最小大小。虽然VARCHAR仅通过使用所需的内容节省了数据页面存储空间,但索引条目仍然使用最大值。对于latin1 VARCHAR(255)列,每行的255个字节。 您的主键大小为512字节。

在改进列的大小后,以下三列上的多列索引将非常适合读取速度(phase, application, start_time)。我们不包括end_time,因为你的复合指数只能达到第一个范围。在阶段和应用之间,首先要有更高的基数(更多的唯一性)。将索引保留在end_time上,因为MySQL可以使用索引合并优化。

然后,为了帮助MySQL退出并让它执行范围扫描,请将OR转换为UNION ALL

SELECT * FROM FILE_INFO 
WHERE PHASE ='DEV' 
AND APPLICATION ='SIALT'
AND (START_TIME <'2014-11-11 08:17:00' AND END_TIME >'2014-11-11 08:17:00')
UNION ALL
SELECT * FROM FILE_INFO 
WHERE PHASE ='DEV' 
AND APPLICATION ='SIALT'
AND (START_TIME <'2014-11-11 08:22:00' AND END_TIME >'2014-11-11 08:22:00')
UNION ALL
SELECT * FROM FILE_INFO 
WHERE PHASE ='DEV' 
AND APPLICATION ='SIALT'
AND (START_TIME >'2014-11-11 08:17:00' AND END_TIME <'2014-11-11 08:22:00')

根据您的数据,您可能还需要强制MySQL使用多列索引(而不是end_time上的索引)。

这样的大型索引需要大量的RAM(整个索引需要在内存中始终保持快速),以及正确的MySQL配置。

答案 2 :(得分:1)

感谢所有建议。

我更改了索引以包含主键中的所有where子句。这还不够。

我看到基于datetime的分区未正确使用。 所以我所做的就是创建一个新的日期字段(基于start_time)。分区对日期很有效(没有时间)。

以下是最终查询:

SELECT * FROM FILE_INFO 
WHERE PHASE ='PDT' AND APPLICATION ='SIALT' 
AND FILE_DATE = '2014-12-10'
AND ((START_TIME <'2014-12-10 08:17:00' AND END_TIME >'2014-12-10 08:17:00') 
OR (START_TIME <'2014-12-10 17:22:00' AND END_TIME >'2014-12-10 17:22:00') 
OR (START_TIME >'2014-12-10 08:17:00' AND END_TIME <'2014-12-10 17:22:00'))

我认为start_time上的索引由于某种原因而无法正常工作。我可以解释非常糟糕的表现。