一、問題
有一張數據表,表數據現在200W條左右。表結構如下:
CREATE TABLE `device_desk` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`running_number` varchar(45) DEFAULT NULL COMMENT '流水號',
`time` timestamp NULL DEFAULT NULL COMMENT '時間',
`temperature` double DEFAULT NULL COMMENT '溫度',
`humidity` double DEFAULT NULL COMMENT '溼度',
`body_infrared` int(11) DEFAULT NULL COMMENT '人體紅外',
`particulate_matter` int(11) DEFAULT NULL COMMENT 'PM2.5',
`air_quality` int(11) DEFAULT NULL COMMENT '空氣質量',
`brightness` int(11) DEFAULT NULL COMMENT '燈光亮度',
`device_id` int(11) DEFAULT NULL COMMENT '設備編號',
`phone` varchar(45) DEFAULT NULL COMMENT '手機號碼',
`origin` varchar(100) DEFAULT NULL COMMENT '原始值',
`color` varchar(45) DEFAULT NULL COMMENT '顏色(用於自定義備註)',
`remark` varchar(45) DEFAULT NULL COMMENT '備註'
PRIMARY KEY (`id`),
KEY `fk_device_idx` (`device_id`),
KEY `index_common_2` (`body_infrared`,`day`,`device_id`,`phone`,`time`),
CONSTRAINT `fk_device2` FOREIGN KEY (`device_id`) REFERENCES `device` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=2123065 DEFAULT CHARSET=utf8 COMMENT='桌子數據';
業務:
需要找出手機號碼爲13800000000,綁定的設備編號爲164,在2015年11月26日有人在的時間段。body_infrared字段0表示無人,1、16、17都表示有人。
查詢語句爲:
select * from device_desk t
where t.body_infrared>0
and t.phone='13800000000'
and t.device_id=164
and t.time like '2015-11-25%'
order by t.time;
得到查詢結果所需耗時爲83秒。
下面我們相辦法把這個查詢時間儘可能縮短。
二、優化過程
1、使用精確的字段代替*
其實我這裏需要獲取的只有時間time,備註remark,和顏色color。
select t.time,t.remark,t.color from device_desk t
where t.body_infrared>0
and t.phone='13800000000'
and t.device_id=164
and t.time like '2015-11-25%'
order by t.time;
特別是使用Hibernate作爲ORM中間件的朋友,會習慣性地使用HQL獲取整表的所有數據,然後部分數據竟然要比獲取所有數據要快。
2、優化時間的模糊查詢
timestamp 類型的字段可以通過類字符串比較的方式來作爲模糊查詢的條件。而MySQL對於like %關鍵字作爲查詢條件是全表掃描的,則沒法使用索引。因爲我要查詢的是當天(某天)的數據,因爲我爲這張表增加一個day字段,用於保存每天的日期。
增加day字段:
ALTER TABLE `time_table`.`device_desk`
ADD COLUMN `day` VARCHAR(45) NULL COMMENT '日期' AFTER `remark`;
爲原有的數據的day字段賦值:
update device_desk t set t.day= DATE_FORMAT(`t`.`time`, '%Y-%m-%d') where t.day is null;
這個操作涉及數據比較多,執行過程會比較長。
更新查詢語句爲:
select t.time,t.remark,t.color from device_desk t
where t.body_infrared>0
and t.phone='13800000000'
and t.device_id=164
and t.day='2015-11-25'
order by t.time;
3、建立索引
爲數據表增加一個組合索引:
ALTER TABLE `time_table`.`device_desk`
ADD INDEX `index_common_2` (`body_infrared` ASC, `day` DESC, `device_id` ASC, `phone` ASC, `time` DESC);
索引建立成功後,執行查詢語句的時候發生需要的時間還是很長。
查看一下查詢語句的執行過程:
explain select t.time,t.remark,t.color from device_desk t
where t.body_infrared>0
and t.phone='13800000000'
and t.device_id=164
and t.day='2015-11-25'
order by t.time;
結果:
這裏發現我們的select語句並沒有使用到我們建立的索引。
經過一番度娘谷哥後,發現MySQL的where條件中用到大於或者小於時,也是進行全表掃描,是不會使用索引查詢的。所以把這個大於查詢更換掉就好。
explain select t.time,t.remark,t.color from device_desk t where (t.body_infrared=1 or t.body_infrared=16 or t.body_infrared=17)
and t.day= '2015-11-25'
and t.device_id=164
and t.phone='13800000000'
order by t.time;
這樣就使用到我們建立的索引了。
查詢效率也得到了大大的改善。