Mysql语句优化--索引
一、开始优化前的准备
一)explain语句
当MySql要执行一个查询语句的时候,它首先会对语句进行语法检查,然后生成一个QEP(Query Execution Plan)
QEP决定了MySql从底层存储引擎获取信息的方式。
语法:explain [extended | partitions] select ......
或者:explain tableName (此时相当与desc语句)
explain select * from artist where name='The Beatles'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: ref
possible_keys: name
key: name
key_len: 257
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec) ERROR:
No query specified
各列详解:
1.key
列出了优化器本次实际使用的索引。
2.possible_keys
优化器可能使用的索引。
3.rows
行数估算值
4.key_len
定义了用于SQL语句的链接条件的键的长度(表明了索引的大小)。
key_len的值只和用在链接和where条件中的索引的列有关。
5.partistions
代表给定表所使用的分区。
二)其他
1.show table status:
用于查看数据库表的底层大小以及表结构,其中包括存储引擎类型、版本、数据和索引大小、行的平均长度及行数。
show table status like 'artist'\G;
这条命令返回的数据准确度取决于使用的数据库引擎。
2.show status:
用于查看MySql服务器的当前内部状态信息。
show global status like 'Created_tmp_%tables';
show session status like 'Created_tmp_%tables';
3.show variables
查看mysql系统变量的当前值,默认在session范围内执行。
show session variables like 'tmp_table_size';
4.show index
查看索引信息
show index from artist\G;
二、单列索引
对select语句where,order by,group by 后的条件所在列添加索引可以提高查询速度。
一)理解索引基数
当一个查询中使用不止一个索引时,Mysql会试图找到一个最高效的索引。
使用到的表:
| artist | CREATE TABLE `artist` (
`artist_id` int(10) unsigned NOT NULL,
`type` enum('Band','Person','Unknown','Combination') NOT NULL,
`name` varchar(255) NOT NULL,
`gender` enum('Male','Female') DEFAULT NULL,
`founded` year(4) DEFAULT NULL,
`country_id` smallint(5) unsigned DEFAULT NULL,
PRIMARY KEY (`artist_id`),
KEY `founded` (`founded`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
explain语句:
explain select * from artist where type='Band' and founded=1980\G;
结果:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: index_merge
possible_keys: founded,type
key: founded,type
key_len: 2,1
ref: NULL
rows: 608
filtered: 100.00
Extra: Using intersect(founded,type); Using where
1 row in set, 1 warning (0.05 sec) ERROR:
No query specified
此时,mysql必须在possible_keys中做出选择,优化器会根据最少工作量的估算开销来选择索引。
我们可以使用索引基数来确定最有可能被选中的索引。
mysql> show index from artist\G;
*************************** 1. row ***************************
Table: artist
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: artist_id
Collation: A
Cardinality: 581100
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: artist
Non_unique: 1
Key_name: founded
Seq_in_index: 1
Column_name: founded
Collation: A
Cardinality: 104
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
*************************** 3. row ***************************
Table: artist
Non_unique: 1
Key_name: type
Seq_in_index: 1
Column_name: type
Collation: A
Cardinality: 3
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
3 rows in set (0.00 sec)
ERROR:
No query specified
其中,Cardinality就是所谓的基数。其中,founded列拥有更高的基数。如果列索引的基数越高,
就说明列中唯一的数量越多,也就可以在选用该索引时,通过更少的读操作找到需要的记录。
当然仅仅知道索引中唯一值的数量意义不大,重要的是要知道该值与总数量的比值。理想情况下该比值为1。
这些信息不但可以帮助我们判断索引是否高效,还可以告诉我们如何在多列索引中对列进行排序。
二)索引与通配符
我们可以通过通配符来做模式匹配工作,但是:
1. 如果查找的词是以通配符开头,则MySql不会使用索引。
mysql> explain select * from artist where name like '%the beatles'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 581100
filtered: 11.11
Extra: Using where
解决方案:
如果你经常需要以通配符开头的查询,常用的方法是在数据库中保存需要查询值的反序值。例如:
想查找以.com结尾的电子邮件地址,首先定义一个reserve_email列存储电子邮件的反序值。查询时
使用:reserve_email like reserve(%.com)
2.MySql不支持基于索引的函数。
mysql> explain select * from artist where upper(name) = 'QUEEN'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 581100
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
因为在name列上使用了upper函数,所以mysql不会使用索引。
三)唯一索引
创建唯一索引的目的:
1)提供数据完整性,以保证列中的任何值只出现一次
2)告知优化器对给定的记录最多只有一行返回结果,避免额外的扫描
四)结果排序
索引也可对查询结果进行排序。基于索引的排序,免去了分类过程,更优。
技巧:也就是说给order by 后面的条件列,可以加索引优化。
五)使用索引链接表
索引的另一个好处是可以提高关系表的链接速度
mysql> explain select ar.name, ar.founded, al.name, al.first_released from artist ar inner join album al using (artist_id) where ar.name = 'Queen'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: ar
partitions: NULL
type: ref
possible_keys: PRIMARY,name_index
key: name_index
key_len: 257
ref: const
rows: 1
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: al
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 552034
filtered: 10.00
Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.12 sec)
为链接条件添加索引
mysql> alter table album add index (artist_id);
Query OK, 0 rows affected (29.24 sec)
Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select ar.name, ar.founded, al.name, al.first_released from artist ar inner join album al using (artist_id) where ar.name = 'Queen'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: ar
partitions: NULL
type: ref
possible_keys: PRIMARY,name_index
key: name_index
key_len: 257
ref: const
rows: 1
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: al
partitions: NULL
type: ref
possible_keys: artist_id
key: artist_id
key_len: 4
ref: music.ar.artist_id
rows: 3
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.03 sec)
二、多列索引
根据国家和专辑类型查询专辑表中的数据,如果我们不使用多列索引,先创建单列索引:
mysql> alter table album
-> add index (country_id),
-> add index (album_type_id);
技巧:创建索引是一件非常耗时的操作,并且会阻塞其他操作。尽可能合并DML语句会获得更高的效率。
mysql> explain select * from album where country_id = 221
-> and album_type_id = 1\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: album
partitions: NULL
type: ref
possible_keys: country_id,album_type_id
key: country_id
key_len: 3
ref: const
rows: 169590
filtered: 50.00
Extra: Using where
1 row in set, 1 warning (0.04 sec)
对album type使用不同值运行同一个查询,那么会使用不同的索引。
使用多列索引,创建多列索引:
mysql> alter table album
-> add index m1
-> (country_id, album_type_id);
重新运行语句:
mysql> explain select * from album where
-> country_id = 221
-> and album_type_id = 1\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: album
partitions: NULL
type: ref
possible_keys: country_id,album_type_id,m1
key: m1
key_len: 7
ref: const,const
rows: 61784
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.06 sec)
创建相反序列的索引:
mysql> alter table album
-> add index m2
-> (album_type_id, country_id);
再次查询,mysql还是使用m1索引。
查找原因:
show index from album\G;
*************************** 1. row ***************************
Table: album
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: album_id
Collation: A
Cardinality: 552034
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: album
Non_unique: 1
Key_name: artist_id
Seq_in_index: 1
Column_name: artist_id
Collation: A
Cardinality: 156672
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 3. row ***************************
Table: album
Non_unique: 1
Key_name: country_id
Seq_in_index: 1
Column_name: country_id
Collation: A
Cardinality: 202
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
*************************** 4. row ***************************
Table: album
Non_unique: 1
Key_name: album_type_id
Seq_in_index: 1
Column_name: album_type_id
Collation: A
Cardinality: 3
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 5. row ***************************
Table: album
Non_unique: 1
Key_name: m1
Seq_in_index: 1
Column_name: country_id
Collation: A
Cardinality: 190
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
*************************** 6. row ***************************
Table: album
Non_unique: 1
Key_name: m1
Seq_in_index: 2
Column_name: album_type_id
Collation: A
Cardinality: 702
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 7. row ***************************
Table: album
Non_unique: 1
Key_name: m2
Seq_in_index: 1
Column_name: album_type_id
Collation: A
Cardinality: 4
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 8. row ***************************
Table: album
Non_unique: 1
Key_name: m2
Seq_in_index: 2
Column_name: country_id
Collation: A
Cardinality: 463
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
技巧:创建多列索引时,交换顺序可能创建出更好的索引。
多列索引除了优化返回的行之外还有更多用途,多列索引最左边的列可以被当作单列索引高效地使用。
当这些列被频繁地用于聚合操作(group by)和排序操作(order by)时,最左边的列也可提升性能。
另外,同样的查询条件下,索引的行应该尽可能短,这样可以提高效率。所以,多余的索引要去掉。
优化器对索引的选择
一般情况下,优化器在一个表上的一次查询只会使用一个索引。但从Mysql5.0版本开始,
优化器可能会使用多个索引。
1)对两个有很高基数的所有使用or操作时,优化器会执行索引的合并操作
mysql> explain select * from artist
-> where name='Queen' or founded=1942\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: index_merge
possible_keys: founded,name_index
key: name_index,founded
key_len: 257,2
ref: NULL
rows: 500
filtered: 100.00
Extra: Using union(name_index,founded); Using where
2)对两个有少量唯一值的索引取交集。
3)
技巧:应该评估使用多列索引,还是让优化器合并索引哪个的效率更高。
查询提示
Mysql中少数几个查询提示会影响性能。
1.总查询提示
总查询提示中只有STRAIGHT_JOIN查询提示会对索引的使用有影响。该提示会让优化器
以指定索引顺序来执行查询计划。例如:
没有STRAIGHT_JOIN提示时:
mysql> explain select al.name, ar.name, al.first_released from artist ar inner join album al using (artist_id) where al.name='Greatest Hits'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: al
partitions: NULL
type: ref
possible_keys: artist_id,name,name_realease
key: name
key_len: 257
ref: const
rows: 660
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: ar
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: music.al.artist_id
rows: 1
filtered: 100.00
Extra: NULL
加入STRAIGHT_JOIN提示:
mysql> explain select STRAIGHT_JOIN al.name, ar.name, al.first_released
-> from artist ar inner join album al using (artist_id)
-> where al.name='Greatest Hits'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: ar
partitions: NULL
type: index
possible_keys: PRIMARY
key: name_index
key_len: 257
ref: NULL
rows: 581100
filtered: 100.00
Extra: Using index
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: al
partitions: NULL
type: ref
possible_keys: artist_id,name,name_realease
key: artist_id
key_len: 4
ref: music.ar.artist_id
rows: 3
filtered: 1.42
Extra: Using where
2.索引提示
索引提示有三种:
USE:使用指定的索引
IGNORE:排除指定的索引
FORCE:让优化器更倾向与索引扫描而不是全表扫描
mysql> explain select * from artist where founded=1980
-> and type='Band'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: ref
possible_keys: founded,a1
key: founded
key_len: 2
ref: const
rows: 1217
filtered: 50.00
Extra: Using index condition; Using where
1 row in set, 1 warning (0.03 sec) ERROR:
No query specified mysql> explain select * from artist
-> ignore index (founded)
-> where founded=1980
-> and type='Band'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: ref
possible_keys: a1
key: a1
key_len: 1
ref: const
rows: 290550
filtered: 0.96
Extra: Using where
技巧:数据量随着时间的变化会改变某些查询的有效性,因此在硬编码的sql语句中加入查询提示是最后考虑的方案。
索引的负面影响
1.影响写操作的性能
2.占用额外的磁盘空间,从而影响备份和恢复策略
3.低效索引占用很大的内存空间
覆盖索引
一个覆盖索引必须包含指定表上包括where,order by,group by以及select语句中的所有列。
使用覆盖索引可以进一步提升查询性能。
技巧:有多种理由可以说服用户不要使用select *。覆盖索引就是原因之一。
另外,由于覆盖索引包含的列很可能很多,因此创建覆盖索引很可能会影响整体
性能。覆盖索引对于那些使用很多较小长度的主码和外键约束的大型规范化模式
来说是理想的优化方式。
局部索引
把一个索引剪切出开头一部分。例如:
mysql> alter table artist
-> drop index name_index,
-> add index name_part(name(20)); mysql> explain select * from artist
-> where name like 'Queen%'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: artist
partitions: NULL
type: range
possible_keys: name_part
key: name_part
key_len: 22
ref: NULL
rows: 93
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.03 sec)