今天一个客户反映网站某个页面很慢。初步分析是数据库查询造成的延时。

打开数据库的慢查询日志,监控到如下语句

 # Time: 120115  1:09:37

# User@Host: sq_ttt[sq_ttt] @ localhost []

# Query_time: 3.895267  Lock_time: 0.000131 Rows_sent: 1  Rows_examined: 168835

use sq_ttt;

SET timestamp=1326560977;

SELECT A.*,A.aid AS id FROM p8_article A  WHERE A.yz=1  AND A.fid IN (31)  AND A.ispic=1 ORDER BY A.list DESC LIMIT 0,1;

这个查询用了3.8s时间。

于是进入数据库开始了这次分析。。。

 

首先看下表结构

CREATE TABLE `p8_article` (

  `aid` mediumint(7) unsigned NOT NULL AUTO_INCREMENT,

  `title` varchar(150) NOT NULL DEFAULT '',

  `smalltitle` varchar(100) NOT NULL DEFAULT '',

  `fid` mediumint(7) unsigned NOT NULL DEFAULT '0',

  `mid` mediumint(5) NOT NULL DEFAULT '0',

  `fname` varchar(50) NOT NULL DEFAULT '',

  `hits` mediumint(7) NOT NULL DEFAULT '0',

  `pages` smallint(4) NOT NULL DEFAULT '0',

  `comments` mediumint(7) NOT NULL DEFAULT '0',

  `posttime` int(10) NOT NULL DEFAULT '0',

  `list` int(10) NOT NULL DEFAULT '0',

  `uid` mediumint(7) NOT NULL DEFAULT '0',

  `username` varchar(30) NOT NULL DEFAULT '',

  `author` varchar(30) NOT NULL DEFAULT '',

  `copyfrom` varchar(100) NOT NULL DEFAULT '',

  `copyfromurl` varchar(150) NOT NULL DEFAULT '',

  `titlecolor` varchar(15) NOT NULL DEFAULT '',

  `fonttype` tinyint(1) NOT NULL DEFAULT '0',

  `picurl` varchar(150) NOT NULL DEFAULT '0',

  `ispic` tinyint(1) NOT NULL DEFAULT '0',

  `yz` tinyint(1) NOT NULL DEFAULT '0',

  `yzer` varchar(30) NOT NULL DEFAULT '',

  `yztime` int(10) NOT NULL DEFAULT '0',

  `levels` tinyint(2) NOT NULL DEFAULT '0',

  `levelstime` int(10) NOT NULL DEFAULT '0',

  `keywords` varchar(100) NOT NULL DEFAULT '',

  `jumpurl` varchar(150) NOT NULL DEFAULT '',

  `iframeurl` varchar(150) NOT NULL DEFAULT '',

  `style` varchar(15) NOT NULL DEFAULT '',

  `template` varchar(255) NOT NULL DEFAULT '',

  `target` tinyint(1) NOT NULL DEFAULT '0',

  `ip` varchar(15) NOT NULL DEFAULT '',

  `lastfid` mediumint(7) NOT NULL DEFAULT '0',

  `money` mediumint(7) NOT NULL DEFAULT '0',

  `buyuser` text NOT NULL,

  `passwd` varchar(32) NOT NULL DEFAULT '',

  `allowdown` varchar(150) NOT NULL DEFAULT '',

  `allowview` varchar(150) NOT NULL DEFAULT '',

  `editer` varchar(30) NOT NULL DEFAULT '',

  `edittime` int(10) NOT NULL DEFAULT '0',

  `begintime` int(10) NOT NULL DEFAULT '0',

  `endtime` int(10) NOT NULL DEFAULT '0',

  `description` text NOT NULL,

  `lastview` int(10) NOT NULL DEFAULT '0',

  `digg_num` mediumint(7) NOT NULL DEFAULT '0',

  `digg_time` int(10) NOT NULL DEFAULT '0',

  `forbidcomment` tinyint(1) NOT NULL DEFAULT '0',

  `ifvote` tinyint(1) NOT NULL DEFAULT '0',

  `heart` varchar(255) NOT NULL DEFAULT '',

  `htmlname` varchar(100) NOT NULL DEFAULT '',

  PRIMARY KEY (`aid`),

  KEY `list` (`list`,`fid`,`yz`,`ispic`) USING BTREE

) ENGINE=MyISAM AUTO_INCREMENT=176567 DEFAULT CHARSET=gbk

 

然后我们采用explain看看是否使用了索引

mysql> explain SELECT A.list,A.aid AS id FROM p8_article A  WHERE A.yz=1  AND A.fid IN (31)  AND A.ispic=1 ORDER BY A.list DESC LIMIT 0,1;

+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+

| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra       |

+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+

|  1 | SIMPLE      | A     | index | NULL          | list | 9       | NULL |    1 | Using where |

+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+

1 row in set (0.00 sec)

可以发现使用了list索引,这个查询类型是index

注意:用explain分析的时候,这个type的速度顺序如下:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般我们优化,最少要到range。


不过今天的问题没有这样简单,因为当我删除索引后,发现即使不用索引也比这个快。

下面是我删掉全部索引的结果

mysql> SELECT A.list,A.aid AS id FROM p8_article A  WHERE A.yz=1  AND A.fid IN (31)  AND A.ispic=1 ORDER BY A.list DESC LIMIT 0,1;

+------------+------+

| list       | id   |

+------------+------+

| 1323887041 | 1312 |

+------------+------+

1 row in set (0.31 sec)

用explain查看没有索引的状态如下
mysql> explain SELECT A.list,A.aid AS id FROM p8_article A  WHERE A.yz=1  AND A.fid IN (31)  AND A.ispic=1 ORDER BY A.list DESC LIMIT 0,1;
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra                       |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
|  1 | SIMPLE      | A     | ALL  | NULL          | NULL | NULL    | NULL | 175175 | Using where; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
1 row in set (0.00 sec)

我们可以看到type是ALL,Extra里面还用了filesort.但即使这样,查询时间才0.31 sec

可以看到加了索引后,反而慢了。这也是我今天想说的一个问题:错误的索引可能会更慢!


调整索引

我们删掉list索引后,尝试重新建立这个复合索引。

mysql> create index list on p8_article(yz,fid,ispic,list);

Query OK, 175175 rows affected (2.80 sec)

Records: 175175  Duplicates: 0  Warnings: 0

这次索引的区别就在于顺序的改变,将list放在了最后。这个可以参考mysql联合索引的最左前缀。

这样在看看explain的状态

mysql> explain SELECT SQL_NO_CACHE A.list,A.aid AS id FROM p8_article A  WHERE A.yz=1  AND A.fid IN (31)  AND A.ispic=1 ORDER BY A.list DESC LIMIT 0,1;

+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+

| id | select_type | table | type | possible_keys | key  | key_len | ref               | rows | Extra       |

+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+

|  1 | SIMPLE      | A     | ref  | list          | list | 5       | const,const,const |   37 | Using where |

+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+

1 row in set (0.00 sec)

type已经是ref了,Extra也不在使用filesort了。
再实际运行查询,看看时间上的变化
mysql> SELECT SQL_NO_CACHE A.list,A.aid AS id FROM p8_article A  WHERE A.yz=1  AND A.fid IN (31)  AND A.ispic=1 ORDER BY A.list DESC LIMIT 0,1;
+------------+------+
| list       | id   |
+------------+------+
| 1323887041 | 1312 |
+------------+------+
1 row in set (0.00 sec)
为了怕有query_cache的原因,所以特意加入了SQL_NO_CACHE。

现在显示查询时间为0.00 sec 基本可以忽略不计了。而经过索引的调整,网页的速度也确实得到了改善。

 

总结:索引错误的使用反而会拖慢查询,甚至比不用还慢。一定要根据最左前缀建立合适的索引,才可以起到加速查询的作用!