首页 > 代码库 > MySQL常用SQL语句优化
MySQL常用SQL语句优化
SQL语句写得不严谨或者不适当,没有正确的使用上索引,会带来很严重的性能问题,这时DBA们又要来收拾这些烂滩子了,所以SQL语句的优化,在日常工作中,是占很重要的一部份,当然还有比如OS优化,硬件优化,MySQL Server优化,数据类型优化,应用层优化。我们进行MySQL的一些相关优化进行探讨。
1、优化数据插入:
DISABLE KEYS和ENABLE KEYS用来关闭或者打开MyISAM表非唯一索引的更新,当用load命令导入数据的时候,适当的设置可以提高导入的速度。只用于MyISAM存储引擎的表。看效果:
mysql> show create table test1\G*************************** 1. row *************************** Table: test1Create Table: CREATE TABLE `test1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` char(20) NOT NULL DEFAULT ‘‘, PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=MyISAM AUTO_INCREMENT=100001 DEFAULT CHARSET=utf81 row in set (0.00 sec)mysql> select count(*) from test1;+----------+| count(*) |+----------+| 300000 |+----------+1 row in set (0.00 sec)mysql>
可以通过这样的挫方式插入30万行数据:
[root ~]$ for ((i=1;i<=300000;i++));do `mysql -uroot -p123456 -e "insert into sakila.test1 values ($i,floor($i+rand()*$i))"` ;done
1. 如果采用以下传统的方式把test1的数据转移到另一相同的表test2上:(用时75s)
mysql> create table test2 like test1;Query OK, 0 rows affected (0.03 sec)mysql> insert into test2 select * from test1; Query OK, 300000 rows affected, 0 warning (1.15 sec)Records: 300000 Duplicates: 0 Warnings: 0
2.先禁用索引, 在完全导入后, 再开启索引,用时29+35=64s
mysql> create table test4 like test1;Query OK, 0 rows affected (0.00 sec)mysql> ALTER TABLE test4 DISABLE KEYS; Query OK, 0 rows affected (0.00 sec)mysql> insert into test4 select * from test1; Query OK, 300000 rows affected, 0 warning (0.29 sec)Records: 300000 Duplicates: 0 Warnings: 0mysql> ALTER TABLE test4 ENABLE KEYS; Query OK, 0 rows affected (0.35 sec)
从上面的测试结果来看, 在大批量导入时先禁用索引, 在完全导入后, 再开启索引, 一次性完成重建索引的效率会相对高很多(当数据量越大时,效果就明显了,实验环境,就不造大量数据了^.^)
对于InnoDB存储引擎表,上面的方式并不能提高导入数据的效率。可以有以下几种方式提高Innodb表的导入效率
(1)因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。
(2)在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性效验,在导入数据结束以后执行SET UNIQUE_CHECKS=1,恢复唯一性效验,可以提高导入效率。
(3)如果使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。
(4)对于有外键约束的表,我们在导入数据之前也可以忽略外键检查,因为innodb的外键是即时检查的,所以对导入的每一行都会进行外键的检查。
2.优化INSERT语句
1) 如果你同时从同一客户插入很多行,使用多个值表的INSERT语句。这比使用分开INSERT语句快(在一些情况中几倍)。
如: Insert into test values(1,2),(1,3),(1,4)…
2) 如果你从不同客户插入很多行,能通过使用INSERT DELAYED语句得到更高的速度。Delayed的含义是让insert 语句马上执行,其实数据都被放在内存的队列中,并没有真正写入磁盘;这比每条语句分别插入要快的多;LOW_PRIORITY刚好相反,在所有其他用户对表的读写完后才进行插入。
3) 将索引文件和数据文件分在不同的磁盘上存放(利用建表中的选项)。
4) 如果进行批量插入,可以增加bulk_insert_buffer_size变量值的方法来提高速度,但是,这只能对myisam表使用。
5) 当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比使用很多INSERT语句快N倍。
下面是基于索引的情况下优化,以下几种情况不会用上索引,这要特别注意的:
• 两个表关联字段类型不一样(也包括长度不一样)
• 通过索引扫描的记录数超过30%,变成全表扫描
• 联合索引中,第一个索引列使用范围查询
• 联合索引中,第一个查询条件不是最左索引列
• 模糊查询条件列最左以通配符 % 开始
• 内存表(HEAP 表)使用HASH索引时,使用范围检索或者ORDER BY
• 两个独立索引,其中一个用于检索,一个用于排序
• 使用了不同的 ORDER BY 和 GROUP BY 表达式
3.优化ORDER BY语句
通过索引排序是性能最好的,通常如果SQL语句不合理,就无法使用索引排序,以下几种情况是无法使用索引排序的。
先看看MySQL官方提供的示例数据库sakila中customer表上的索引情况
mysql> show index from customer;+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| customer | 0 | PRIMARY | 1 | customer_id | A | 599 | NULL | NULL | | BTREE | | || customer | 1 | idx_fk_store_id | 1 | store_id | A | 4 | NULL | NULL | | BTREE | | || customer | 1 | idx_fk_address_id | 1 | address_id | A | 599 | NULL | NULL | | BTREE | | || customer | 1 | idx_last_name | 1 | last_name | A | 599 | NULL | NULL | | BTREE | | |+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+4 rows in set (0.03 sec)
1、MySQL中有两种排序方式
mysql> explain select customer_id from customer order by store_id ;+----+-------------+----------+-------+---------------+-----------------+---------+------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+-------+---------------+-----------------+---------+------+------+-------------+| 1 | SIMPLE | customer | index | NULL | idx_fk_store_id | 1 | NULL | 599 | Using index |+----+-------------+----------+-------+---------------+-----------------+---------+------+------+-------------+1 row in set (0.03 sec)
因为查询主键,然后store_id列是辅助索引(二级索引),辅助索引上存放了索引键值+对应行的主键,所以直接扫描辅助索引返回有序数据。
mysql> explain select * from customer order by customer_id;+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+| 1 | SIMPLE | customer | index | NULL | PRIMARY | 2 | NULL | 599 | NULL |+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+1 row in set (0.00 sec)
这种排序方式直接使用了主键,也可以说成是使用了聚集索引。因为innodb是索引组织表(index-organized table),通过主键聚集数据,数据都是按照主键排序存放。而聚集索引就是按照没张表的主键构造一颗B+树,同时叶子节点中存放的即为正张表的行记录数据,也将聚集索引的叶子节点称为数据页。聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分。
第二种是通过对返回数据进行排序,也就是通常说的Filesort排序,所有不是通过索引直接返回排序结果的排序都叫Filesort排序。Filesort并不代表通过磁盘文件进行排序,而只是说明进行了一个排序操作,至于排序操作是否使用了磁盘文件或者临时表,取决于mysql服务器对排序参数的设置和需要排序数据的大小。
mysql> explain select * from customer order by store_id;+----+-------------+----------+------+---------------+------+---------+------+------+----------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+------+---------------+------+---------+------+------+----------------+| 1 | SIMPLE | customer | ALL | NULL | NULL | NULL | NULL | 599 | Using filesort |+----+-------------+----------+------+---------------+------+---------+------+------+----------------+1 row in set (0.00 sec)
那么这里优化器为什么不使用store_id列上的辅助索引进行排序呢?
当通过辅助索引来查找数据时,innodb存储引擎会遍历辅助索引并通过叶级别的指针获得指向主键索引的主键,然后通过主键索引来找到一个完整的行记录。举例来说,如果在一棵高度为3的辅助索引树中查找数据,那么需要对这个辅助索引树遍历3次找到指定主键,如果聚集索引树的高度同样为3,那么还需要对聚集索引树进行3次查找,最终找到一个完整的行数所在的页,因此一共需要6次逻辑IO访问以得到最终的一个数据页。
2、查询的where和order by中的列无法组合成索引的最左前缀;
mysql> alter table customer add key idx_stored_email ( store_id , email );Query OK, 0 rows affected (0.20 sec)Records: 0 Duplicates: 0 Warnings: 0mysql> explain select store_id , email from customer order by email ; +----+-------------+----------+-------+---------------+------------------+---------+------+------+-----------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+-------+---------------+------------------+---------+------+------+-----------------------------+| 1 | SIMPLE | customer | index | NULL | idx_stored_email | 154 | NULL | 599 | Using index; Using filesort |+----+-------------+----------+-------+---------------+------------------+---------+------+------+-----------------------------+1 row in set (0.00 sec)
这里为什么又是filesort呢?不是使用了using index吗?虽然使用了覆盖索引(只访问索引的查询,即查询只需要访问索引,而无须访问数据行,最简单的理解,比如翻开一本书,从目录页查找某些内容,但是目录就写的比较详细,我们在目录就找到了自己想看的内容)。但是请别忘记了,idx_stored_email是复合索引,必须遵循最左前缀的原则。
修改成以下情况就可以看到效果了:
mysql> explain select store_id , email from customer order by store_id;+----+-------------+----------+-------+---------------+------------------+---------+------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+-------+---------------+------------------+---------+------+------+-------------+| 1 | SIMPLE | customer | index | NULL | idx_stored_email | 154 | NULL | 599 | Using index |+----+-------------+----------+-------+---------------+------------------+---------+------+------+-------------+1 row in set (0.00 sec)
简单来说,尽量减少额外排序,通过索引直接返回有序数据,where条件和order by使用相同的索引,并且order by的顺序和索引顺序相同。
正确写法:
mysql> explain select store_id , email , customer_id from customer where store_id =1 order by store_id desc; +----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+-------------+| 1 | SIMPLE | customer | ref | idx_fk_store_id,idx_stored_email | idx_stored_email | 1 | const | 325 | Using index |+----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+-------------+1 row in set (0.00 sec)
不当写法:
mysql> explain select store_id , email , customer_id from customer where store_id =1 order by customer_id desc; +----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+------------------------------------------+| 1 | SIMPLE | customer | ref | idx_fk_store_id,idx_stored_email | idx_stored_email | 1 | const | 325 | Using where; Using index; Using filesort |+----+-------------+----------+------+----------------------------------+------------------+---------+-------+------+------------------------------------------+1 row in set (0.00 sec)
3、查询在索引列的第一列上是范围条件,不会用上索引;
mysql> explain select * from customer where store_id <5;+----+-------------+----------+------+----------------------------------+------+---------+------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+------+----------------------------------+------+---------+------+------+-------------+| 1 | SIMPLE | customer | ALL | idx_fk_store_id,idx_stored_email | NULL | NULL | NULL | 599 | Using where |+----+-------------+----------+------+----------------------------------+------+---------+------+------+-------------+1 row in set (0.00 sec)mysql>
还有一种情况就是:查询条件上有多个等于条件。对排序来说,这也是一种范围查询
4.优化GROUP BY 语句
默认情况下,mysql对所有GROUP BY col1,col2,的字段进行排序。这与在查询中指定ORDER BY col1,col2类似。因此,如果显式包括一个 包含相同列的ORDER BY子句,则对mysql的实际性能没有什么影响。如果查询包括GROUP BY,但我们想要避免排序带来的性能损耗,则可以指定ORDER BY NULL禁止排序,示例如下:
mysql> explain select payment_date , sum(amount) from payment group by payment_date;+----+-------------+---------+------+---------------+------+---------+------+-------+---------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+------+---------------+------+---------+------+-------+---------------------------------+| 1 | SIMPLE | payment | ALL | NULL | NULL | NULL | NULL | 16086 | Using temporary; Using filesort |+----+-------------+---------+------+---------------+------+---------+------+-------+---------------------------------+1 row in set (0.18 sec)
可以看见使用了Filesort,还使用了内存临时表,这条SQL严重影响性能,所以需要优化:
使用ORDER BY NULL禁止排序
mysql> explain select payment_date , sum(amount) from payment group by payment_date order by null;+----+-------------+---------+------+---------------+------+---------+------+-------+-----------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+------+---------------+------+---------+------+-------+-----------------+| 1 | SIMPLE | payment | ALL | NULL | NULL | NULL | NULL | 16086 | Using temporary |+----+-------------+---------+------+---------------+------+---------+------+-------+-----------------+1 row in set (0.00 sec)
可以看见已经没有使用Filesort,但是还是使用了内存临时表,这是我们可以创建一个复合索引来优化性能
mysql> alter table payment add key idx_pal (payment_date,amount);Query OK, 0 rows affected (1.27 sec)Records: 0 Duplicates: 0 Warnings: 0mysql> explain select payment_date, sum(amount) from payment group by payment_date;+----+-------------+---------+-------+---------------+---------+---------+------+-------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+-------+---------------+---------+---------+------+-------+-------------+| 1 | SIMPLE | payment | index | NULL | idx_pal | 8 | NULL | 16086 | Using index |+----+-------------+---------+-------+---------------+---------+---------+------+-------+-------------+1 row in set (0.03 sec)
5.优化子查询
MySQL 4.1开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另外一个SELECT语句中。使用子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也非常easy,but,在有些情况下,子查询效率非常低下,我们可以使用比较高大上的写法,那就是连接(JOIN)取而代之
mysql> explain select * from customer where customer_id not in ( select customer_id from payment);+----+--------------------+----------+----------------+--------------------+--------------------+---------+------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+--------------------+----------+----------------+--------------------+--------------------+---------+------+------+-------------+| 1 | PRIMARY | customer | ALL | NULL | NULL | NULL | NULL | 599 | Using where || 2 | DEPENDENT SUBQUERY | payment | index_subquery | idx_fk_customer_id | idx_fk_customer_id | 2 | func | 13 | Using index |+----+--------------------+----------+----------------+--------------------+--------------------+---------+------+------+-------------+2 rows in set (0.11 sec)
我解释一下这里的执行计划:
第二行,id为2,说明优先级最高,最先执行,DEPENDENT SUBQUERY子查询中的第一个SELECT(意味着select依赖于外层查询中的数据),type为index_subquery,与unique_subquery类似,区别在于in的后面是查询非唯一索引字段的子查询,using index使用了覆盖索引。
第一行,id为1,说明优先级最低,可以看见select_type列是PRIMARY,意思是最外层的SELECT查询,可以看见使用了全表扫描。
如果使用连接(join)来完成这个查询,速度将会快很多。尤其是连接条件有索引的情况下:
mysql> explain select * from customer left join payment on customer.customer_id = payment.customer_id where payment.customer_id is null;+----+-------------+----------+------+--------------------+--------------------+---------+-----------------------------+------+-------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------+------+--------------------+--------------------+---------+-----------------------------+------+-------------------------+| 1 | SIMPLE | customer | ALL | NULL | NULL | NULL | NULL | 599 | NULL || 1 | SIMPLE | payment | ref | idx_fk_customer_id | idx_fk_customer_id | 2 | sakila.customer.customer_id | 13 | Using where; Not exists |+----+-------------+----------+------+--------------------+--------------------+---------+-----------------------------+------+-------------------------+2 rows in set (0.03 sec)
从执行计划看出查询关联类型从index_subquery调整为了ref,在mysql5.5(包含mysql5.5),子查询效率还是不如关联查询(join),连接之所以更有效率,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。
6.优化OR条件
对于含有OR的查询语句,则无法使用单列索引,但是可以使用复合索引
mysql> explain select * from film where language_id=1 or title =‘ACADEMY DINOSAUR‘;+----+-------------+-------+------+------------------------------+------+---------+------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-------+------+------------------------------+------+---------+------+------+-------------+| 1 | SIMPLE | film | ALL | idx_title,idx_fk_language_id | NULL | NULL | NULL | 1000 | Using where |+----+-------------+-------+------+------------------------------+------+---------+------+------+-------------+1 row in set (0.05 sec)mysql>
mysql> show create table test\G*************************** 1. row *************************** Table: testCreate Table: CREATE TABLE `test` ( `id` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, KEY `id` (`id`), KEY `age` (`age`)) ENGINE=InnoDB DEFAULT CHARSET=utf81 row in set (0.00 sec)mysql> select * from test;+------+------+| id | age |+------+------+| 1 | 23 || 2 | 36 |+------+------+2 rows in set (0.00 sec)mysql>
可以看见表test有两个单列索引,我们使用如下SQL查询,看是否会使用索引
mysql> explain select * from test where id=1 or age=36; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-------+------+---------------+------+---------+------+------+-------------+| 1 | SIMPLE | test | ALL | id,age | NULL | NULL | NULL | 2 | Using where |+----+-------------+-------+------+---------------+------+---------+------+------+-------------+1 row in set (0.00 sec)mysql>
可以看见虽然显示有id,age索引可用,但是没有使用,即全表扫描。我们可以这样优化:
mysql> explain select * from test where id=1 union all select * from test where age=36; +----+--------------+------------+------+---------------+------+---------+-------+------+-----------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+--------------+------------+------+---------------+------+---------+-------+------+-----------------+| 1 | PRIMARY | test | ref | id | id | 5 | const | 1 | NULL || 2 | UNION | test | ref | age | age | 5 | const | 1 | NULL || NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | Using temporary |+----+--------------+------------+------+---------------+------+---------+-------+------+-----------------+3 rows in set (0.06 sec)mysql>
可以看见已经使用了索引
看看使用复合索引查询的情况:
mysql> show create table test\G*************************** 1. row *************************** Table: testCreate Table: CREATE TABLE `test` ( `id` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, KEY `id_2` (`id`,`age`)) ENGINE=InnoDB DEFAULT CHARSET=utf81 row in set (0.00 sec)mysql>
复合索引使用上了索引:
mysql> explain select * from test where id=1 or age=36; +----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+| 1 | SIMPLE | test | index | id_2 | id_2 | 10 | NULL | 2 | Using where; Using index |+----+-------------+-------+-------+---------------+------+---------+------+------+--------------------------+1 row in set (0.00 sec)mysql>
7.优化分页查询(LIMIT)
一般分页查询时,通过创建覆盖索引能够比较好的提高性能。比较常见的一个分页查询是limit 1000,20,这种最蛋碎了,此时mysql排序出前1020条记录后仅仅返回第1001到1020条记录,前1000条记录都会被抛弃,查询和排序的代价非常高。
在索引上完成排序分页操作,最后根据主键关联回表查询所需要的其他列内容。(使用到了自连接)例如下面的SQL语句:
一般而言,分页SQL的耗时随着 start 值的增加而急剧增加;可以参考http://imysql.com/2014/07/26/mysql-optimization-case-paging-optimize.shtml
mysql> explain select film_id , description from film order by title limit 50,5;+----+-------------+-------+------+---------------+------+---------+------+------+----------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-------+------+---------------+------+---------+------+------+----------------+| 1 | SIMPLE | film | ALL | NULL | NULL | NULL | NULL | 1000 | Using filesort |+----+-------------+-------+------+---------------+------+---------+------+------+----------------+1 row in set (0.00 sec)
可以看见实际上使用了全表扫描,如果表有上百万记录,那么这将是一条致命SQL
我们改写成按照索引分页后回表读取行的方式,从执行计划中看不到全表扫描了
mysql> explain select a.film_id , a.description from film a inner join (select film_id from film order by title limit 50,5) b on a.film_id=b.film_id; +----+-------------+------------+--------+---------------+-----------+---------+-----------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+------------+--------+---------------+-----------+---------+-----------+------+-------------+| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 55 | NULL || 1 | PRIMARY | a | eq_ref | PRIMARY | PRIMARY | 2 | b.film_id | 1 | NULL || 2 | DERIVED | film | index | NULL | idx_title | 767 | NULL | 1000 | Using index |+----+-------------+------------+--------+---------------+-----------+---------+-----------+------+-------------+3 rows in set (0.04 sec)
执行计划解释:
第三行:id为2,优先级最高,最先执行 select_type为DERIVED 用来表示包含在from子句中的子查询的select,mysql会递归执行并将结果放到一个临时表中。服务器内部称为“派生表”,因为该临时表是从子查询中派生出来的。 type列为index表示索引树全扫描,mysql遍历整个索引来查询匹配的行,这里就把film_id查询出来了。 Extra列为using index 表示使用覆盖索引
第二行: select_type为PRIMARY,即复杂查询的最外层,当然这里还不算是最最外层。 table列为a,即film表的别名a, type列为eq_ref,类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
第一行: select_type列的primary表示该查询为外层查询 table列被标记为<derived2>,表示查询结果来自一个衍生表,其中2代表该查询衍生自第2个select查询,即id为2的select
优化相关参考资料:http://wubx.net/category/optimize/
http://imysql.com/?s=MySQL%E4%BC%98%E5%8C%96
http://www.cnblogs.com/gomysql/p/3632209.html
MySQL常用SQL语句优化