“昨日杏”通过精心收集,向本站投稿了5篇实例讲解MySQL数据库的查询优化技术,下面是小编精心整理后的实例讲解MySQL数据库的查询优化技术,仅供参考,大家一起来看看吧。

实例讲解MySQL数据库的查询优化技术

篇1:实例讲解MySQL数据库的查询优化技术 (1)

虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表,

实例讲解MySQL数据库的查询优化技术 (1)

因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:

篇2:经典MySQL的limit查询优化数据库教程

实例讲解MySQL数据库的查询优化技术

以下的文章主要是对MySQL limit查询优化的具体内容的介绍,我们大家都知道MySQL数据库的优化是相当重要的,其他最为常用也是最为需要优化的就是limit。MySQL的limit给分页带来了极大的方便,但数据量一大的时候,limit的性能就急剧下降。

同样是取10条数据

select * from yanxue8_visit limit 10000,10select * from yanxue8_visit limit 0,10

就不是一个数量级别的。

网上也很多关于limit的五条优化准则,都是翻译自MySQL手册,虽然正确但不实用。今天发现一篇文章写了些关于limit优化的,很不错。

文中不是直接使用limit,而是首先获取到offset的id然后直接使用limit size来获取数据。根据他的数据,明显要好于直接使用limit。这里我具体使用数据分两种情况进行测试。(测试环境win2033+p4双核 (3GHZ) +4G内存MySQLlimit查询)

1、offset比较小的时候

1.select * from yanxue8_visit limit 10,10

多次运行,时间保持在0.0004-0.0005之间

Select * From yanxue8_visit Where vid >=(Select vid From yanxue8_visit Order By vid limit 10,1  ) limit 10

多次运行,时间保持在0.0005-0.0006之间,主要是0.0006

结论:偏移offset较小的时候,直接使用limit较优,

这个显然是子查询的原因。

2、offset大的时候

select * from yanxue8_visit limit 10000,10

多次运行,时间保持在0.0187左右

Select * From yanxue8_visit Where vid >=(Select vid From yanxue8_visit Order By vid limit 10000,1  ) limit 10

多次运行,时间保持在0.0061左右,只有前者的1/3。可以预计offset越大,后者越优。

以后要注意改正自己的limit语句,优化一下MySQL了

推荐人评论

MySQL的优化是非常重要的。其他最常用也最需要优化的就是limit。MySQL的limit给分页带来了极大的方便,但数据量一大的时候,limit的性能就急剧下降。

以上的相关内容就是对MySQLlimit查询优化 的介绍,望你能有所收获。

篇3:MySQL查询优化explain

在分析查询性能时,考虑EXPLAIN关键字同样很管用,EXPLAIN关键字一般放在SELECT查询语句的前面,用于描述MySQL如何执行查询操作、以及MySQL成功返回结果集需要执行的行数。explain 可以帮助我们分析 select 语句,让我们知道查询效率低下的原因,从而改进我们查询,让查询优化器能够更好的工作。

一、MySQL 查询优化器是如何工作的

MySQL 查询优化器有几个目标,但是其中最主要的目标是尽可能地使用索引,并且使用最严格的索引来消除尽可能多的数据行。最终目标是提交 SELECT 语句查找数据行,而不是排除数据行。优化器试图排除数据行的原因在于它排除数据行的速度越快,那么找到与条件匹配的数据行也就越快。如果能够首先进行最严格的测试,查询就可以执行地更快。

EXPLAIN 的每个输出行提供一个表的相关信息,并且每个行包括下面的列:

项说明idMySQL Query Optimizer 选定的执行计划中查询的序列号。表示查询中执行 select 子句或操作表的顺序,id值越大优先级越高,越先被执行。id 相同,执行顺序由上至下。select_type 查询类型说明SIMPLE简单的 select 查询,不使用 union 及子查询PRIMARY最外层的 select 查询UNIONUNION 中的第二个或随后的 select 查询,不 依赖于外部查询的结果集DEPENDENT UNIONUNION 中的第二个或随后的 select 查询,依 赖于外部查询的结果集SUBQUERY子查询中的第一个 select 查询,不依赖于外 部查询的结果集DEPENDENT SUBQUERY子查询中的第一个 select 查询,依赖于外部 查询的结果集DERIVED用于 from 子句里有子查询的情况。 MySQL 会 递归执行这些子查询, 把结果放在临时表里。UNCACHEABLE SUBQUERY结果集不能被缓存的子查询,必须重新为外 层查询的每一行进行评估。UNCACHEABLE UNIONUNION 中的第二个或随后的 select 查询,属 于不可缓存的子查询项说明table输出行所引用的表type 重要的项,显示连接使用的类型,按最 优到最差的类型排序说明system表仅有一行(=系统表)。这是 const 连接类型的一个特例。constconst 用于用常数值比较 PRIMARY KEY 时。当 查询的表仅有一行时,使用 System。eq_refconst 用于用常数值比较 PRIMARY KEY 时。当 查询的表仅有一行时,使用 System。ref连接不能基于关键字选择单个行,可能查找 到多个符合条件的行。 叫做 ref 是因为索引要 跟某个参考值相比较。这个参考值或者是一 个常数,或者是来自一个表里的多表查询的 结果值ref_or_null如同 ref, 但是 MySQL 必须在初次查找的结果 里找出 null 条目,然后进行二次查找。index_merge说明索引合并优化被使用了。unique_subquery在某些 IN 查询中使用此种类型,而不是常规的 ref:value IN (SELECT primary_key FROM single_table WHERE some_expr)index_subquery在 某 些 IN 查 询 中 使 用 此 种 类 型 , 与 unique_subquery 类似,但是查询的是非唯一 性索引: value IN (SELECT key_column FROM single_table WHERE some_expr)range只检索给定范围的行,使用一个索引来选择 行。key 列显示使用了哪个索引。当使用=、 、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操作符,用常量比较关键字列时,可 以使用 range。index全表扫描,只是扫描表的时候按照索引次序 进行而不是行。主要优点就是避免了排序, 但是开销仍然非常大。all最坏的情况,从头到尾全表扫描。项说明possible_keys指出 MySQL 能在该表中使用哪些索引有助于 查询。如果为空,说明没有可用的索引。项说明keyMySQL 实际从 possible_key 选择使用的索引。 如果为 NULL,则没有使用索引。很少的情况 下,MYSQL 会选择优化不足的索引。这种情 况下,可以在 SELECT 语句中使用 USE INDEX (indexname)来强制使用一个索引或者用 IGNORE INDEX(indexname)来强制 MYSQL 忽略索引项说明key_len使用的索引的长度。在不损失精确性的情况 下,长度越短越好。项说明ref显示索引的哪一列被使用了项说明rowsMYSQL 认为必须检查的用来返回请求数据的行数项说明rowsMYSQL 认为必须检查的用来返回请求数据的行数

extra 中出现以下 2 项意味着 MYSQL 根本不能使用索引,效率会受到重大影响。应尽可能对此进行优化。

extra 项说明Using filesort表示 MySQL 会对结果使用一个外部索引排序,而不是从表里按索引次序读到相关内容。可能在内存或者磁盘上进行排序。MySQL 中无法利用索引完成的排序操作称为“文件排序”Using temporary表示 MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。

下面来举一个例子来说明下 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。

先查查试试看:

>EXPLAINSELECT author_idFROM `article`WHERE category_id = 1 AND comments > 1ORDER BY views DESCLIMIT 1G

看看部分输出结果:

>*************************** 1. row ***************************           id: 1  select_type: SIMPLE        table: article         type: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 3        Extra: Using where; Using filesort1 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: rangepossible_keys: x          key: x      key_len: 8          ref: NULL         rows: 1        Extra: Using where; Using filesort1 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: refpossible_keys: y          key: y      key_len: 4          ref: const         rows: 1        Extra: Using where1 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脚本:

然后来看一个左连接查询:

explain select * from class left join book on class.card = book.cardG

分析结果是:

>*************************** 1. row ***************************           id: 1  select_type: SIMPLE        table: class         type: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 0        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: book         type: ALLpossible_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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: book         type: refpossible_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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: book         type: ALLpossible_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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: class         type: refpossible_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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: book         type: ALLpossible_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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: class         type: refpossible_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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: book         type: ALLpossible_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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: book         type: ALLpossible_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 需要优化左表。

我们再来看看三表查询的例子

添加一个新索引:

1

>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: ALLpossible_keys: NULL          key: NULL      key_len: NULL          ref: NULL         rows: 20000        Extra: *************************** 2. row ***************************           id: 1  select_type: SIMPLE        table: book         type: refpossible_keys: y          key: y      key_len: 4          ref: test.class.card         rows: 1000        Extra: *************************** 3. row ***************************           id: 1  select_type: SIMPLE        table: phone         type: refpossible_keys: z          key: z      key_len: 4          ref: test.book.card         rows: 260        Extra: Using index3 rows in set (0.00 sec)

后 2 行的 type 都是 ref 且总 rows 优化很好,效果不错。

MySql 中的 explain 语法可以帮助我们改写查询,优化表的结构和索引的设置,从而最大地提高查询效率。当然,在大规模数据量时,索引的建立和维护的代价也是很高的,往往需要较长的时间和较大的空间,如果在不同的列组合上建立索引,空间的开销会更大。因此索引最好设置在需要经常查询的字段中。

篇4:mysql优化查询的方法

1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引,

2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num=0

3.应尽量避免在 where 子句中使用!=或操作符,否则将引擎放弃使用索引而进行全表扫描。

4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20

可以这样查询:

select id from t where num=10

union all

select id from t where num=20

5.in 和 not in 也要慎用,否则会导致全表扫描,如:

select id from t where num in(1,2,3)

对于连续的数值,能用 between 就不要用 in 了:

select id from t where num between 1 and 3

6.下面的查询也将导致全表扫描:

select id from t where name like '%abc%'

若要提高效率,可以考虑全文检索。

7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num=@num

可以改为强制查询使用索引:

select id from t with(index(索引名)) where num=@num

8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2=100

应改为:

select id from t where num=100*2

9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3)='abc'--name以abc开头的id

select id from t where datediff(day,createdate,'-11-30')=0--‘2005-11-30’生成的id

应改为:

select id from t where name like 'abc%'

select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'

10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致,

12.不要写一些没有意义的查询,如需要生成一个空表结构:

select col1,col2 into #t from t where 1=0

这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:

create table #t(...)

13.很多时候用 exists 代替 in 是一个好的选择:

select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)

14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。

17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

21.避免频繁创建和删除临时表,以减少系统表资源的消耗。

22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

29.尽量避免大事务操作,提高系统并发能力。

30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

篇5:MySQL查询优化技术系列讲座之使用索引

索引是提高查询速度的最重要的工具,当然还有其它的一些技术可供使用,但是一般来说引起最大性能差异的都是索引的正确使用。在MySQL邮件列表中,人们经常询问那些让查询运行得更快的方法。在大多数情况下,我们应该怀疑数据表上有没有索引,并且通常在添加索引之后立即解决了问题。当然,并不总是这样简单就可以解决问题的,因为优化技术本来就并非总是简单的。然而,如果没有使用索引,在很多情况下,你试图使用其它的方法来提高性能都是在浪费时间。首先使用索引来获取最大的性能提高,接着再看其它的技术是否有用。

这一部分讲述了索引是什么以及索引是怎么样提高查询性能的。它还讨论了在某些环境中索引可能降低性能,并为你明智地选择数据表的索引提供了一些指导方针。在下一部分中我们将讨论MySQL查询优化器,它试图找到执行查询的效率最高的方法。了解一些优化器的知识,作为对如何建立索引的补充,对我们是有好处的,因为这样你才能更好地利用自己所建立的索引。某些编写查询的方法实际上让索引不起作用,在一般情况下你应该避免这种情形的发生。

索引的优点

让我们开始了解索引是如何工作的,首先有一个不带索引的数据表。不带索引的表仅仅是一个无序的数据行集合。例如,图1显示的ad表就是不带索引的表,因此如果需要查找某个特定的公司,就必须检查表中的每个数据行看它是否与目标值相匹配。这会导致一次完全的数据表扫描,这个过程会很慢,如果这个表很大,但是只包含少量的符合条件的记录,那么效率会非常低。

图1:无索引的ad表

图2是同样的一张数据表,但是增加了对ad表的company_num数据列的索引。这个索引包含了ad表中的每个数据行的条目,但是索引的条目是按照company_num值排序的。现在,我们不是逐行查看以搜寻匹配的数据项,而是使用索引。假设我们查找公司13的所有数据行。我们开始扫描索引并找到了该公司的三个值。接着我们碰到了公司14的索引值,它比我们正在搜寻的值大。索引值是排过序的,因此当我们读取了包含14的索引记录的时候,我们就知道再也不会有更多的匹配记录,可以结束查询操作了。因此使用索引获得的功效是:我们找到了匹配的数据行在哪儿终止,并能够忽略其它的数据行。另一个功效来自使用定位算法查找第一条匹配的条目,而不需要从索引头开始执行线性扫描(例如,二分搜索就比线性扫描要快一些)。通过使用这种方法,我们可以快速地定位第一个匹配的值,节省了大量的搜索时间。数据库使用了多种技术来快速地定位索引值,但是在本文中我们不关心这些技术。重点是它们能够实现,并且索引是个好东西。

图2:索引后的ad表

你可能要问,我们为什么不对数据行进行排序从而省掉索引?这样不是也能实现同样的搜索速度的改善吗?是的,如果表只有一个索引,这样做也可能达到相同的效果。但是你可能添加第二个索引,那么就无法一次使用两种不同方法对数据行进行排序了(例如,你可能希望在顾客名称上建立一个索引,在顾客ID号或电话号码上建立另外一个索引)。把与数据行相分离的条目作为索引解决了这个问题,允许我们创建多个索引。此外,索引中的行一般也比数据行短一些。当你插入或删除新的值的时候,移动较短的索引值比移动较长数据行的排序次序更加容易。

不同的MySQL存储引擎的索引实现的具体细节信息是不同的。例如,对于MyISAM数据表,该表的数据行保存在一个数据文件中,索引值保存在索引文件中。一个数据表上可能有多个索引,但是它们都被存储在同一个索引文件中。索引文件中的每个索引都包含一个排序的键记录(它用于快速地访问数据文件)数组。

与此形成对照的是,BDB和InnoDB存储引擎没有使用这种方法来分离数据行和索引值,尽管它们也把索引作为排序后的值集合进行操作。在默认情况下,BDB引擎使用单个文件存储数据和索引值。InnoDB使用单个数据表空间(tablespace),在表空间中管理所有InnoDB表的数据和索引存储。我们可以把InnoDB配置为每个表都在自己的表空间中创建,但是即使是这样,数据表的数据和索引也存储在同一个表空间文件中。

前面的讨论描述了单个表查询环境下的索引的优点,在这种情况下,通过减少对整个表的扫描,使用索引明显地提高了搜索的速度。当你运行涉及多表联结(jion)查询的时候,索引的价值就更高了。在单表查询中,你需要在每个数据列上检查的值的数量是表中数据行的数量。在多表查询中,这个数量可能大幅度上升,因为这个数量是这些表中数据行的数量所产生的。

假设你拥有三个未索引的表t1、t2和t3,每个表都分别包含数据列i1、i2和i3,并且每个表都包含了1000条数据行,其序号从1到1000。查找某些值匹配的数据行组合的查询可能如下所示:

SELECT t1.i1, t2.i2, t3.i3
FROM t1, t2, t3
WHERE t1.i1 = t2.i2 AND t2.i1 = t3.i3;

这个查询的结果应该是1000行,每个数据行包含三个相等的值。如果在没有索引的情况下处理这个查询,那么如果我们不对这些表进行全部地扫描,我们是没有办法知道哪些数据行含有哪些值的。因此你必须尝试所有的组合来查找符合WHERE条件的记录。可能的组合的数量是1000 x 1000 x 1000(10亿!),它是匹配记录的数量的一百万倍。这就浪费了大量的工作。这个例子显示,如果没有使用索引,随着表的记录不断增长,处理这些表的联结所花费的时间增长得更快,导致性能很差。我们可以通过索引这些数据表来显著地提高速度,因为索引让查询采用如下所示的方式来处理:

1.选择表t1中的第一行并查看该数据行的值。

2.使用表t2上的索引,直接定位到与t1的值匹配的数据行。类似地,使用表t3上的索引,直接定位到与表t2的值匹配的数据行。

3.处理表t1的下一行并重复前面的过程。执行这样的操作直到t1中的所有数据行都被检查过。

在这种情况下,我们仍然对表t1执行了完整的扫描,但是我们可以在t2和t3上执行索引查找,从这些表中直接地获取数据行。理论上采用这种方式运行上面的查询会快一百万倍。当然这个例子是为了得出结论来人为建立的。然而,它解决的问题却是现实的,给没有索引的表添加索引通常会获得惊人的性能提高。

MySQL有几种使用索引的方式:

· 如上所述,索引被用于提高WHERE条件的数据行匹配或者执行联结操作时匹配其它表的数据行的搜索速度。

· 对于使用了MIN()或MAX()函数的查询,索引数据列中最小或最大值可以很快地找到,不用检查每个数据行。

· MySQL利用索引来快速地执行ORDER BY和GROUP BY语句的排序和分组操作。

· 有时候MySQL会利用索引来读取查询得到的所有信息。假设你选择了MyISAM表中的被索引的数值列,那么就不需要从该数据表中选择其它的数据列。在这种情况下,MySQL从索引文件中读取索引值,它所得到的值与读取数据文件得到的值是相同的。没有必要两次读取相同的值,因此没有必要考虑数据文件。

索引的代价

一般来说,如果MySQL能够找到方法,利用索引来更快地处理查询,它就会这样做。这意味着,对于大多数情况,如果你没有对表进行索引,就会使性能受到损害。这就是我所描绘的索引优点的美景。但是它有缺点吗?有的,它在时间和空间上都有开销。在实践中,索引的优点的价值一般会超过这些缺点,但是你也应该知道到底有一些什么缺点。

首先,索引加快了检索的速度,但是减慢了插入和删除的速度,同时还减慢了更新被索引的数据列中的值的速度。也就是说,索引减慢了大多数涉及写操作的速度。发生这种现象的原因在于写入一条记录的时候不但需要写入数据行,还需要改变所有的索引。数据表带有的索引越多,需要做出的修改就越多,平均性能的降低程度也就越大。在本文的“高效率载入数据”部分中,我们将更细致地了解这些现象并找出处理方法。

其次,索引会花费磁盘空间,多个索引相应地花费更多的磁盘空间。这可能导致更快地到达数据表的大小限制:

· 对于MyISAM表,频繁地索引可能引起索引文件比数据文件更快地达到最大限制。

· 对于BDB表,它把数据和索引值一起存储在同一个文件中,添加索引引起这种表更快地达到最大文件限制。

在InnoDB的共享表空间中分配的所有表都竞争使用相同的公共空间池,因此添加索引会更快地耗尽表空间中的存储。但是,与MyISAM和BDB表使用的文件不同,InnoDB共享表空间并不受操作系统的文件大小限制,因为我们可以把它配置成使用多个文件。只要有额外的磁盘空间,你就可以通过添加新组件来扩展表空间。

使用单独表空间的InnoDB表与BDB表受到的约束是一样的,因为它的数据和索引值都存储在单个文件中,

这些要素的实际含义是:如果你不需要使用特殊的索引帮助查询执行得更快,就不要建立索引。

选择索引

假设你已经知道了建立索引的语法,但是语法不会告诉你数据表应该如何索引。这要求我们考虑数据表的使用方式。这一部分指导你如何识别出用于索引的备选数据列,以及如何最好地建立索引:

用于搜索、排序和分组的索引数据列并不仅仅是用于输出显示的。换句话说,用于索引的最好的备选数据列是那些出现在WHERE子句、join子句、ORDER BY或GROUP BY子句中的列。仅仅出现在SELECT关键字后面的输出数据列列表中的数据列不是很好的备选列:

SELECT
col_a <- 不是备选列
FROM
tbl1 LEFT JOIN tbl2
ON tbl1.col_b = tbl2.col_c <- 备选列
WHERE
col_d = expr; <- 备选列

当然,显示的数据列与WHERE子句中使用的数据列也可能相同。我们的观点是输出列表中的数据列本质上不是用于索引的很好的备选列。

Join子句或WHERE子句中类似col1 = col2形式的表达式中的数据列都是特别好的索引备选列。前面显示的查询中的col_b和col_c就是这样的例子。如果MySQL能够利用联结列来优化查询,它一定会通过减少整表扫描来大幅度减少潜在的表-行组合。

考虑数据列的基数(cardinality)。基数是数据列所包含的不同值的数量。例如,某个数据列包含值1、3、7、4、7、3,那么它的基数就是4。索引的基数相对于数据表行数较高(也就是说,列中包含很多不同的值,重复的值很少)的时候,它的工作效果最好。如果某数据列含有很多不同的年龄,索引会很快地分辨数据行。如果某个数据列用于记录性别(只有“M”和“F”两种值),那么索引的用处就不大。如果值出现的几率几乎相等,那么无论搜索哪个值都可能得到一半的数据行。在这些情况下,最好根本不要使用索引,因为查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。惯用的百分比界线是“30%”。现在查询优化器更加复杂,把其它一些因素也考虑进去了,因此这个百分比并不是MySQL决定选择使用扫描还是索引的唯一因素。

索引较短的值。尽可能地使用较小的数据类型。例如,如果MEDIUMINT足够保存你需要存储的值,就不要使用BIGINT数据列。如果你的值不会长于25个字符,就不要使用CHAR(100)。较小的值通过几个方面改善了索引的处理速度:

· 较短的值可以更快地进行比较,因此索引的查找速度更快了。

· 较小的值导致较小的索引,需要更少的磁盘I/O。

· 使用较短的键值的时候,键缓存中的索引块(block)可以保存更多的键值。MySQL可以在内存中一次保持更多的键,在不需要从磁盘读取额外的索引块的情况下,提高键值定位的可能性。

对于InnoDB和BDB等使用聚簇索引(clustered index)的存储引擎来说,保持主键(primary key)短小的优势更突出。聚簇索引中数据行和主键值存储在一起(聚簇在一起)。其它的索引都是次级索引;它们存储主键值和次级索引值。次级索引屈从主键值,它们被用于定位数据行。这暗示主键值都被复制到每个次级索引中,因此如果主键值很长,每个次级索引就需要更多的额外空间。

索引字符串值的前缀(prefixe)。如果你需要索引一个字符串数据列,那么最好在任何适当的情况下都应该指定前缀长度。例如,如果有CHAR(200)数据列,如果前面10个或20个字符都不同,就不要索引整个数据列。索引前面10个或20个字符会节省大量的空间,并且可能使你的查询速度更快。通过索引较短的值,你可以获得那些与比较速度和磁盘I/O节省相关的好处。当然你也需要利用常识。仅仅索引某个数据列的第一个字符串可能用处不大,因为如果这样操作,那么在索引中不会有太多的唯一值。

你可以索引CHAR、VARCHAR、BINARY、VARBINARY、BLOB和TEXT数据列的前缀。

使用最左(leftmost)前缀。建立多列复合索引的时候,你实际上建立了MySQL可以使用的多个索引。复合索引可以作为多个索引使用,因为索引中最左边的列集合都可以用于匹配数据行。这种列集合被称为“最左前缀”(它与索引某个列的前缀不同,那种索引把某个列的前面几个字符作为索引值)。

假设你在表的state、city和zip数据列上建立了复合索引。索引中的数据行按照state/city/zip次序排列,因此它们也会自动地按照state/city和state次序排列。这意味着,即使你在查询中只指定了state值,或者指定state和city值,MySQL也可以使用这个索引。因此,这个索引可以被用于搜索如下所示的数据列组合:

state, city, zip
state, city
state

MySQL不能利用这个索引来搜索没有包含在最左前缀的内容。例如,如果你按照city或zip来搜索,就不会使用到这个索引。如果你搜索给定的state和具体的ZIP代码(索引的1和3列),该索引也是不能用于这种组合值的,尽管MySQL可以利用索引来查找匹配的state从而缩小搜索的范围。

不要过多地索引。不要认为“索引越多,性能越高”,不要对每个数据列都进行索引。我们在前面提到过,每个额外的索引都会花费更多的磁盘空间,并降低写操作的性能。当你修改表的内容的时候,索引就必须被更新,甚至可能重新整理。如果你的索引很少使用或永不使用,你就没有必要减小表的修改操作的速度。此外,为检索操作生成执行计划的时候,MySQL会考虑索引。建立额外的索引会给查询优化器增加更多的工作量。如果索引太多,有可能(未必)出现MySQL选择最优索引失败的情况。维护自己必须的索引可以帮助查询优化器来避免这类错误。

如果你考虑给已经索引过的表添加索引,那么就要考虑你将增加的索引是否是已有的多列索引的最左前缀。如果是这样的,不用增加索引,因为已经有了(例如,如果你在state、city和zip上建立了索引,那么没有必要再增加state的索引)。

让索引类型与你所执行的比较的类型相匹配。在你建立索引的时候,大多数存储引擎会选择它们将使用的索引实现。例如,InnoDB通常使用B树索引。MySQL也使用B树索引,它只在三维数据类型上使用R树索引。但是,MEMORY存储引擎支持散列索引和B树索引,并允许你选择使用哪种索引。为了选择索引类型,需要考虑在索引数据列上将执行的比较操作类型:

· 对于散列(hash)索引,会在每个数据列值上应用散列函数。生成的结果散列值存储在索引中,并用于执行查询。散列函数实现的算法类似于为不同的输入值生成不同的散列值。使用散列值的好处是散列值比原始值的比较效率更高。散列索引用于执行=或<=>操作等精确匹配的时候速度非常快。但是对于查询一个值的范围效果就非常差了:

id < 30
weight BETWEEN 100 AND 150

· B树索引可以用于高效率地执行精确的或者基于范围(使用操作<、<=、=、>=、>、、!=和BETWEEN)的比较。B树索引也可以用于LIKE模式匹配,前提是该模式以文字串而不是通配符开头。

如果你使用的MEMORY数据表只进行精确值查询,散列索引是很好的选择。这是MEMORY表使用的默认的索引类型,因此你不需要特意指定。如果你希望在MEMORY表上执行基于范围的比较,应该使用B树索引。为了指定这种索引类型,需要给索引定义添加USING BTREE。例如:

CREATE TABLE lookup
(
id INT NOT NULL,
name CHAR(20),
PRIMARY KEY USING BTREE (id)
) ENGINE = MEMORY;

如果你希望执行的语句的类型允许,单个MEMORY表可以同时拥有散列索引和B树索引,即使在同一个数据列上。

有些类型的比较不能使用索引。如果你只是通过把值传递到函数(例如STRCMP())中来执行比较操作,那么对它进行索引就没有价值。服务器必须计算出每个数据行的函数值,它会排除数据列上索引的使用。

使用慢查询(slow-query)日志来识别执行情况较差的查询。这个日志可以帮助你找出从索引中受益的查询。你可以直接查看日志(它是文本文件),或者使用mysqldumpslow工具来统计它的内容。如果某个给定的查询多次出现在“慢查询”日志中,这就是一个线索,某个查询可能没有优化编写。你可以重新编写它,使它运行得更快。你要记住,在评估“慢查询”日志的时候,“慢”是根据实际时间测定的,在负载较大的服务器上“慢查询”日志中出现的查询会多一些。

阅读剩余 0%
本站所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。 用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈本站将在三个工作日内改正。