首页 > 代码库 > MySql查询优化方法总结
MySql查询优化方法总结
常用查询优化
1: max()优化: 在相应列上添加索引
2: count()优化:count(*) 会算出包含null记录的数量, count(field_name)只包含不含 null的数量(这也是很多时候两种count方式结果不一致的原因), count()的时候尽量用后一种, count(null)返回0,即不会记录null记录数量
3: 子查询优化=====》(改为)联接查询(如果1对多的关系,注意重复记录)
4: group by优化 如果包含子查询,在子查询里面使用where条件和group by过滤, 避免在复杂查询的最外层使用group by(如果最外层使用会用到临时表)
5: order by , limit 优化:
方式1:尽量使用主键或有索引的列order by;
方式2: 使用自增型的字段: 记录上一次返回的主键或者自增列(此种方式该字段不能有空值,否则会出现有的页面数量不足的问题, 解决的方式是添加附加的index_id, 自增且索引), 过滤时先用大于上一次主键值且小于上一次的主键值+每页的数量, 过滤该字段,然后order by 和limit
PS: 复合索引有效条件:
1: where 条件中依次过滤(最左前缀)
2:排序时: 索引字段有正有反的时候不能使用
3:排序时: 某列有范围查询的时候该列右侧的字段不能使用索引
优化的思路就是尽量避免扫描过多的记录。
创建索引的原则:
1: where, order by ,group by, on从句中的字段
2:索引字段越小越好
3:联合索引时把离散程度高的字段放前面
表级优化
1: 表的范式优化
2: 适当增减一些冗余, 做反范式优化(以空间换取时间)
3: 表的列非常多的时候使用垂直拆分
原则:
1: 把不常用的单独字段放到一个表中
2: 把大字段独立存放到一个表中
3: 把经常一起用的字段放在一起
4: 表的数据量非常大的时候使用水平拆分
方法:
1: 根据某个字段进行hash预算, 如果要拆分成5个表, 用取余的方式取到0-4,分表保到相应的表中
2: 针对不同的hashID把数据存到不同的表中
问题:
1: 跨分区查询的问题
2: 统计及后台报表操作
(前台使用分表查询, 后台使用汇总表查询做汇总报表操作).
一、 通过查询缓冲提高查询速度
一般我们使用SQL语句进行查询时,数据库服务器每次在收到客户端发来SQL后,都会执行这条SQL语句。但当在一定间隔内(如1分钟内),接到完全一样的SQL语句,也同样执行它。虽然这样可以保证数据的实时性,但在大多数时候,数据并不要求完全的实时,也就是说可以有一定的延时。如果是这样的话,在短时间内执行完全一样的SQL就有些得不偿失。
幸好MySQL为我们提供了查询缓冲的功能(只能在MySQL 4.0.1及以上版本使用查询缓冲)。我们可以通过查询缓冲在一定程度上提高查询性能。
1、我们可以通过在MySQL安装目录中的my.ini文件设置查询缓冲:
设置也非常简单,只需要将query_cache_type设为1即可。在设置了这个属性后,MySQL在执行任何SELECT语句之前,都会在它的缓冲区中查询是否在相同的SELECT语句被执行过,如果有,并且执行结果没有过期,那么就直接取查询结果返回给客户端。但在写SQL语句时注意,MySQL的查询缓冲是区分大小写的。如下列的两条SELECT语句:
SELECT * FROM TABLE1
SELECT * FROM TABLE1
上面的两条SQL语句对于查询缓冲是完全不同的SELECT。而且查询缓冲并不自动处理空格,因此,在写SQL语句时,应尽量减少空格的使用,尤其是在SQL首和尾的空格(因为,查询缓冲并不自动截取首尾空格)。
2、临时关闭查询缓冲方法:
虽然不设置查询缓冲,有时可能带来性能上的损失,但有一些SQL语句需要实时地查询数据,或者并不经常使用(可能一天就执行一两次)。这样就需要把缓冲关了。当然,这可以通过设置query_cache_type的值来关闭查询缓冲,但这就将查询缓冲永久地关闭了。
在MySQL 5.0中提供了一种可以临时关闭查询缓冲的方法:SQL_NO_CACHE。
SELECT SQL_NO_CACHE field1, field2 FROM TABLE1
以上的SQL语句由于使用了SQL_NO_CACHE,因此,不管这条SQL语句是否被执行过,服务器都不会在缓冲区中查找,每次都会执行它。
3、临时开启查询缓冲方法:
我们还可以将my.ini中的query_cache_type设成2,这样只有在使用了SQL_CACHE后,才使用查询缓冲。
SELECT SQL_CALHE * FROM TABLE1
二、MySQL对查询的自动优化
索引对于数据库是非常重要的。在查询时可以通过索引来提高性能。但有时使用索引反而会降低性能。我们可以看如下的SALES表:
CREATETABLE SALES
(
ID INT(10) UNSIGNED NOTNULL AUTO_INCREMENT,
NAME VARCHAR(100) NOTNULL,
PRICE FLOATNOTNULL,
SALE_COUNT INTNOTNULL,
SALE_DATE DATE NOTNULL,
PRIMARYKEY(ID),
INDEX (NAME),
INDEX (SALE_DATE)
)
假设这个表中保存了数百万条数据,而我们要查询商品号为1000的商品在2004年和2005年的平均价格。我们可以写如下的SQL语句:
SELECT AVG(PRICE) FROM SALES
WHERE ID=1000 AND SALE_DATE BETWEEN ‘2004-01-01‘ AND ‘2005-12-31‘;
如果这种商品的数量非常多,差不多占了SALES表的记录的50%或更多。那么使用SALE_DATE字段上索引来计算平均数就有些慢。因为如果使用索引,就得对索引进行排序操作。当满足条件的记录非常多时(如占整个表的记录的50%或更多的比例),速度会变慢,这样还不如对整个表进行扫描。因此,MySQL会自动根据满足条件的数据占整个表的数据的比例自动决定是否使用索引进行查询。
对于MySQL来说,上述的查询结果占整个表的记录的比例是30%左右时就不使用索引了,这个比例是MySQL的开发人员根据他们的经验得出的。然而,实际的比例值会根据所使用的数据库引擎不同而不同。
三、 基于索引的排序
MySQL的弱点之一是它的排序。虽然MySQL可以在1秒中查询大约15,000条记录,但由于MySQL在查询时最多只能使用一个索引。因此,如果WHERE条件已经占用了索引,那么在排序中就不使用索引了,这将大大降低查询的速度。我们可以看看如下的SQL语句:
SELECT*FROM SALES WHERE NAME = ‘name‘ ORDERBY SALE_DATE DESC;
在以上的SQL的WHERE子句中已经使用了NAME字段上的索引,因此,在对SALE_DATE进行排序时将不再使用索引。为了解决这个问题,我们可以对SALES表建立复合索引:
ALTERTABLE SALES DROPINDEX NAME, ADDINDEX (NAME, SALE_DATE)
这样再使用上述的SELECT语句进行查询时速度就会大副提升。但要注意,在使用这个方法时,要确保WHERE子句中没有排序字段,在上例中就是不能用SALE_DATE进行查询,否则虽然排序快了,但是SALE_DATE字段上没有单独的索引,因此查询又会慢下来。
SELECT*FROM SALES WHERE NAME = ‘name1‘ AND NAME = ‘name2‘
以上的查询语句要查找NAME既等于name1又等于name2的记录。很明显,这是一个不可达的查询,WHERE条件一定是假。MySQL在执行SQL 语句之前,会先分析WHERE条件是否是不可达的查询,如果是,就不再执行这条SQL语句了。为了验证这一点。我们首先对如下的SQL使用EXPLAIN 进行测试:
EXPLAIN SELECT*FROM SALES WHERE NAME = ’name1‘
上面的查询是一个正常的查询,我们可以看到使用EXPLAIN返回的执行信息数据中table项是SALES。这说明MySQL对SALES进行操作了。再看看下面的语句:
EXPLAIN SELECT*FROM SALES WHERE NAME = ’name1‘ AND NAME = ‘name2‘
我们可以看到,table项是空,这说明MySQL并没有对SALES表进行操作。
四、 使用各种查询选择来提高性能
SELECT语句除了正常的使用外,MySQL还为我们提供了很多可以增强查询性能的选项。如上面介绍的用于控制查询缓冲的SQL_NO_CACHE和SQL_CACHE就是其中两个选项。在这一部分,我将介绍几个常用的查询选项。
1、STRAIGHT_JOIN:强制连接顺序
当我们将两个或多个表连接起来进行查询时,我们并不用关心MySQL先连哪个表,后连哪个表。而这一切都是由MySQL内部通过一系列的计算、评估,最后得出的一个连接顺序决定的。如下列的SQL语句中,TABLE1和TABLE2并不一定是谁连接谁:
SELECT TABLE1.FIELD1, TABLE2.FIELD2 FROM TABLE1 ,TABLE2 WHERE …
如果开发人员需要人为地干预连接的顺序,就得使用STRAIGHT_JOIN关键字,如下列的SQL语句:
SELECT TABLE1.FIELD1, TABLE2.FIELD2 FROM TABLE1 STRAIGHT_JOIN TABLE2 WHERE …
由上面的SQL语句可知,通过STRAIGHT_JOIN强迫MySQL按TABLE1、TABLE2的顺序连接表。如果你认为按自己的顺序比MySQL推荐的顺序进行连接的效率高的话,就可以通过STRAIGHT_JOIN来确定连接顺序。
2、干预索引使用,提高性能
在上面已经提到了索引的使用。一般情况下,在查询时MySQL将自己决定是否使用索引,使用哪一个索引。
但在一些特殊情况下,我们希望MySQL只使用一个或几个索引,或者不希望使用某个索引。这就需要使用MySQL的控制索引的一些查询选项。
(1)限制使用索引的范围:
有时我们在数据表里建立了很多索引,当MySQL对索引进行选择时,这些索引都在考虑的范围内。但有时我们希望MySQL只考虑几个索引,而不是全部的索引,这就需要用到USE INDEX对查询语句进行设置。
SELECT*FROM TABLE1 USEINDEX (FIELD1, FIELD2) …
从以上SQL语句可以看出,无论在TABLE1中已经建立了多少个索引,MySQL在选择索引时,只考虑在FIELD1和FIELD2上建立的索引。
(2)限制不使用索引的范围:
如果我们要考虑的索引很多,而不被使用的索引又很少时,可以使用IGNORE INDEX进行反向选取。在上面的例子中是选择被考虑的索引,而使用IGNORE INDEX是选择不被考虑的索引。
SELECT*FROM TABLE1 IGNORE INDEX (FIELD1, FIELD2) …
在上面的SQL语句中,TABLE1表中只有FIELD1和FIELD2上的索引不被使用。
(3)强迫使用某一个索引:
上面的两个例子都是给MySQL提供一个选择,也就是说MySQL并不一定要使用这些索引。而有时我们希望MySQL必须要使用某一个索引(由于MySQL在查询时只能使用一个索引,因此只能强迫MySQL使用一个索引)。这就需要使用FORCE INDEX来完成这个功能。
SELECT*FROM TABLE1 FORCE INDEX (FIELD1) …
以上的SQL语句只使用建立在FIELD1上的索引,而不使用其它字段上的索引。
3. 使用临时表提供查询性能
当我们查询的结果集中的数据比较多时,可以通过SQL_BUFFER_RESULT选项强制将结果集放到临时表中,这样就可以很快地释放MySQL的表锁(这样其它的SQL语句就可以对这些记录进行查询了),并且可以长时间地为客户端提供大记录集。
SELECT SQL_BUFFER_RESULT * FROM TABLE1 WHERE …
和SQL_BUFFER_RESULT选项类似的还有SQL_BIG_RESULT,这个选项一般用于分组或DISTINCT关键字,这个选项通知MySQL,如果有必要,就将查询结果放到临时表中,甚至在临时表中进行排序。
SELECT SQL_BUFFER_RESULT FIELD1, COUNT(*) FROM TABLE1 GROUPBY FIELD1
五、MYSQL查询优化:使用索引
MySQL有几种使用索引的方式:
· 如上所述,索引被用于提高WHERE条件的数据行匹配或者执行联结操作时匹配其它表的数据行的搜索速度。
· 对于使用了MIN()或MAX()函数的查询,索引数据列中最小或最大值可以很快地找到,不用检查每个数据行。
· MySQL利用索引来快速地执行ORDER BY和GROUP BY语句的排序和分组操作。
· 有时候MySQL会利用索引来读取查询得到的所有信息。假设你选择了MyISAM表中的被索引的数值列,那么就不需要从该数据表中选择其它的数据列。在这种情况下,MySQL从索引文件中读取索引值,它所得到的值与读取数据文件得到的值是相同的。没有必要两次读取相同的值,因此没有必要考虑数据文件。
索引创建规则:
1、表的主键、外键必须有索引;
2、数据量超过300的表应该有索引;
3、经常与其他表进行连接的表,在连接字段上应该建立索引;
4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
5、索引应该建在选择性高的字段上;
6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
7、复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替:
A、正确选择复合索引中的主列字段,一般是选择性较好的字段;
B、复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;
C、如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
D、如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段;
E、如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
8、频繁进行数据操作的表,不要建立太多的索引;
9、删除无用的索引,避免对执行计划造成负面影响;
以上是一些普遍的建立索引时的判断依据。一言以蔽之,索引的建立必须慎重,对每个索引的必要性都应该经过仔细分析,要有建立的依据。
因为太多的索引与不充分、不正确的索引对性能都毫无益处:在表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。
另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。
查询优化之explain的深入解析
下面来举一个例子来说明下 explain 的用法。
先来一张表:
CREATE TABLE IF NOT EXISTS `article` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`author_id` int(10) unsigned NOT NULL,
`category_id` int(10) unsigned NOT NULL,
`views` int(10) unsigned NOT NULL,
`comments` int(10) unsigned NOT NULL,
`title` varbinary(255) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
);
再插几条数据:
INSERT INTO `article`
(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
(1, 1, 1, 1, ‘1‘, ‘1‘),
(2, 2, 2, 2, ‘2‘, ‘2‘),
(1, 1, 3, 3, ‘3‘, ‘3‘);
需求:
查询 category_id 为 1 且 comments 大于 1 的情况下,views 最多的 article_id。
先查查试试看:
EXPLAIN
SELECT author_id
FROM `article`
WHERE category_id = 1 AND comments > 1
ORDER BY views DESC
LIMIT 1\G
看看部分输出结果:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: article
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 3
Extra: Using where; Using filesort
1 row in set (0.00 sec)
很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
嗯,那么最简单的解决方案就是加索引了。好,我们来试一试。查询的条件里即 where 之后共使用了 category_id,comments,views 三个字段。那么来一个联合索引是最简单的了。
ALTER TABLE `article` ADD INDEX x ( `category_id` , `comments`, `views` );
结果有了一定好转,但仍然很糟糕:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: article
type: range
possible_keys: x
key: x
key_len: 8
ref: NULL
rows: 1
Extra: Using where; Using filesort
1 row in set (0.00 sec)
type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。但是我们已经建立了索引,为啥没用呢?这是因为按照 BTree 索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。当 comments 字段在联合索引里处于中间位置时,因comments > 1 条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。
那么我们需要抛弃 comments,删除旧索引:
DROP INDEX x ON article;
然后建立新索引:
ALTER TABLE `article` ADD INDEX y ( `category_id` , `views` ) ;
接着再运行查询:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: article
type: ref
possible_keys: y
key: y
key_len: 4
ref: const
rows: 1
Extra: Using where
1 row in set (0.00 sec)
可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。
再来看一个多表查询的例子。
首先定义 3个表 class 和 room。
CREATE TABLE IF NOT EXISTS `class` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`bookid`)
);
CREATE TABLE IF NOT EXISTS `phone` (
`phoneid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`phoneid`)
) engine = innodb;
然后再分别插入大量数据。插入数据的php脚本:
<?php
$link = mysql_connect("localhost","root","870516");
mysql_select_db("test",$link);
for($i=0;$i<10000;$i++)
{
$j = rand(1,20);
$sql = " insert into class(card) values({$j})";
mysql_query($sql);
}
for($i=0;$i<10000;$i++)
{
$j = rand(1,20);
$sql = " insert into book(card) values({$j})";
mysql_query($sql);
}
for($i=0;$i<10000;$i++)
{
$j = rand(1,20);
$sql = " insert into phone(card) values({$j})";
mysql_query($sql);
}
mysql_query("COMMIT");
?>
然后来看一个左连接查询:
explain select * from class left join book on class.card = book.card\G
分析结果是:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
2 rows in set (0.00 sec)
显然第二个 ALL 是需要我们进行优化的。
建立个索引试试看:
ALTER TABLE `book` ADD INDEX y ( `card`);
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ref
possible_keys: y
key: y
key_len: 4
ref: test.class.card
rows: 1000
Extra:
2 rows in set (0.00 sec)
可以看到第二行的 type 变为了 ref,rows 也变成了 1741*18,优化比较明显。这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引。
删除旧索引:
DROP INDEX y ON book;
建立新索引。
ALTER TABLE `class` ADD INDEX x ( `card`);
结果
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
2 rows in set (0.00 sec)
基本无变化。
然后来看一个右连接查询:
explain select * from class right join book on class.card = book.card;
分析结果是:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ref
possible_keys: x
key: x
key_len: 4
ref: test.book.card
rows: 1000
Extra:
2 rows in set (0.00 sec)
优化较明显。这是因为 RIGHT JOIN 条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立索引。
删除旧索引:
DROP INDEX x ON class;
建立新索引。
ALTER TABLE `book` ADD INDEX y ( `card`);
结果
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
2 rows in set (0.00 sec)
基本无变化。
最后来看看 inner join 的情况:
explain select * from class inner join book on class.card = book.card;
结果:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ref
possible_keys: x
key: x
key_len: 4
ref: test.book.card
rows: 1000
Extra:
2 rows in set (0.00 sec)
删除旧索引:
DROP INDEX y ON book;
结果
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
2 rows in set (0.00 sec)
建立新索引。
ALTER TABLE `class` ADD INDEX x ( `card`);
结果
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
2 rows in set (0.00 sec)
综上所述,inner join 和 left join 差不多,都需要优化右表。而 right join 需要优化左表。
我们再来看看三表查询的例子
添加一个新索引:
ALTER TABLE `phone` ADD INDEX z ( `card`);
ALTER TABLE `book` ADD INDEX y ( `card`);
explain select * from class left join book on class.card=book.card left join phone on book.card = phone.card;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: class
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: book
type: ref
possible_keys: y
key: y
key_len: 4
ref: test.class.card
rows: 1000
Extra:
*************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: phone
type: ref
possible_keys: z
key: z
key_len: 4
ref: test.book.card
rows: 260
Extra: Using index
3 rows in set (0.00 sec)
后 2 行的 type 都是 ref 且总 rows 优化很好,效果不错。
MySql查询优化方法总结