索引是一种可以让SELECT语句提高效率的数据结构,可以起到快速定位的作用。
给表列创建索引:
- 建表时创建索引:
- create table t(id int,name varchar(20),index idx_name (name));
- 给表追加索引:
- alter table t add unique index idx_id(id);
- 给表的多列上追加索引
- alter table t add index idx_id_name(id,name);
或者:
- create index idx_id_name on t(id,name);
查看索引
- 使用show语句查看t表上的索引:
- show index from t;
或者:
- show keys from t;
–mysql中索引也被称作keys
- 使用show create table语句查看索引:
- show create table t\G
删除索引:
- 使用alter table命令删除索引:
- alter table 表 drop index 索引名
- 使用drop index命令删除索引:
- drop index 索引名 on 表
例如一个学生信息表,我们设置学号(stu_id)为索引:
关于MySQL服务器是如何执行SQL语句的相关信息可以用explain语句来查看,可以用explain语句查看执行过程的语句主要有select、insert、update、delete等,其使用方式是explain后接具体的增删改查SQL语句。
例如:explain select * from test.t;
其返回形式为数据表,如下图所示:
其中每个字段代表的含义如下:
通过:type、possible_keys和key三个字段,我们能清楚的知道查询语句是否使用了索引和使用了哪个索引。
列名 | 说明 |
---|---|
id | SQL语句标识符,如果仅包含一个表的简单查询id始终为1;如果查询是多表连接查询,id按照顺便往下排 |
select_type | 查询类型,例如:SIMPLE(简单查询不包括子查询和union运算符) |
table | 语句相关的表名 |
partitions | 语句相关的表分区,非分区表始终为NULL |
type | 语句中表对象间的连接类型。如果值为all代表使用低效的全表扫方式;如果值为const代表仅有一行匹配的结果记录;如果值为range代表使用了索引范围查找 |
possible_keys | 执行时有可能用到的索引名称 |
key | 执行时实际用到的索引名称 |
key_len | 执行时使用索引的长度 |
ref | 哪些表列或常数列与“key”列出的索引名进行了比较操作 |
rows | 执行的语句所涉及的行记录数,该数值对于INNODB引擎的表为估算值不是实际真实值 |
filtered | 返回结果的行记录数占执行中所有读取到的行记录数的百分比,该数值为估算值,最高为100 |
Extra | 执行SQL语句时一些相关的补充信息 |
假设有一库为test,其中有一个以百万行记录的t表,t表的ID列建有索引。比较以下两种SQL语句书写方式,比较运行时间:
方式一: select * from t where id+1<5;
方式二: select * from t where id<5-1;
方式一结果如下:
用时0.62秒,通过explain查看使用的查询key发现并没有使用索引来进行查询:
方式二结果如下:
用时小于0.00秒,通过explain查看使用的查询key发现使用了索引进行查询。
从实验结果看出,如果采用方式一(运行时间0.62秒),使用采用表达式的方式作为查询条件,条件列中的索引会失效,即便返回行数非常少,优化器也会使用低效的全表扫方式来解析SQL语句;如果同样效果的语句采用方式二的写法,索引不会失效,查询效率高。
发生这种事情的深层原因在于:
大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。 但是当使用表达式的时候,就会不使用缓存,因为这些函数的返回是会不定的易变的。
比较以下两种SQL语句书写方式,比较运行时间:
方式一:select * from t where id=1 or id=2 or id=3;
方式二:select * from t where id in (1,2,3);
由于t表id列已添加索引,可以使用MySQL自带压力测试工具mysqlslap,增加并发用户数量和查询次数比较两种方式的运行效率。
将方式一和方式二的SQL语句,使用mysqlslap进行测试,采用100个并发客户端,所有客户端一共运行5000次,可以写成以下方式:
方式一:
mysqlslap -uroot -p --create-schrma=test --query='select * from t where id=1 or id=2 or id=3' --concurrency=100 --number-of-queries=5000
方式二:
mysqlslap -uroot -p --create-schema=test --query='select * from t where id in (1,2,3)' --concurrency=100 --number-of-queries=5000
测试结果如下:
从实验结果可以看出,SQL语句采用方式一的or方式50个并发用户执行5000次查询所用的时间为1.09秒;SQL语句采用方式二的in方式50个并发用户执行5000次查询所用的时间为0.93秒,在写法等效的情况下,使用IN来替代OR效率更高。
继续以上实验,从t表中仅要找出id值为1,2,3的行,因为id值连续,可以使用以下第三种方式书写SQL语句:
select * from t where id between 1and 3
使用mysqlslap验证执行效率:
mysqlslap -uroot -p --create-schema=test --query='select * from t where id between 1 and 3' --concurrency=100 --number-of-queries=5000
结果如下:
SQL语句采用方式三between的写法,在50个并发用户执行5000次查询的测试时间为0.885秒,参照前述实验的测试结果,执行效率进一步提高。可以看出如果条件列表数值连续的情况下,SQL语句采用BETWEEN的方式比用IN方式效率更高。
MySQL数据库中使用union或union all运算符将一个或多个列数相同的查询结果集上下合并成为一个查询结果集。其中union会合并各个结果集中相同的记录行,重复记录只显示一次外加自动排序,而union all运算符不去重不排序。因此,对于无重复记录的多个查询结果集应当使用union all合并。参见如下实验:
select * from t where id=1 union select * from t where id=2;
select * from t where id=1 union all select * from t where id=2;
使用mysqlslap测试:
mysqlslap -uroot -p --create-schema=test --query='select * from t where id=1 union select * from t where id=2' --concurrency=100 --number-of-queries=5000;
mysqlslap -uroot -p --create-schema=test --query='select * from t where id=1 union all select * from t where id=2' --concurrency=100 --number-of-queries=5000;
在50个并发用户执行5000次查询的测试中执行效率有所差异,如下图所示:
从测试结果可以看出,使用union运算符由于需要去除重复记录和排序,查询时间为1.229秒高于union all运算符的1.120秒。因此,对于无重复记录的结果集使用union all合并的效率要高。
在SELECT查询语句中,where子句和having子句都起到对行记录过滤的作用,主要区别在于having子句是对group by子句产生的结果(可能包含聚合函数),而where子句先于having子句运行,主要目的是缩减查询结果集的行记录数。实验需要复杂一些的数据表,可以通过http://downloads.mysql.com/docs/world.sql.zip链接下载MySQL例子数据库。该数据库包含,country(国家),city(城市)和countrylanguage(国家语言)三张数据表。例子数据库下载解压后包含world.sql文件,使用mysql客户端的source命令运行后,会创建包含前述三张表的world数据库。
mysql> select Code,Name from country where Name='China';
中国的国家编号是“CHN”,如果要在city(城市)表中统计中国的城市数量,可以通过以下两种方式SQL语句获取,其结果集相同:
select CountryCode,count(*) from city where CountryCode='CHN';
select CountryCode,count(*) from city group by CountryCode having CountryCode='CHN';
mysqlslap -uroot -p --concurrency=100 --iterations=1 --number-of-queries=5000 --create-schema=world --query="select CountryCode,count(*) from city where CountryCode='CHN'"
mysqlslap -uroot -p --concurrency=100 --iterations=1 --number-of-queries=5000 --create-schema=world --query="select CountryCode,count(*) from city group by CountryCode having CountryCode='CHN'"
在书写SQL语句时,如果在where或having子句中使用like模糊匹配操作符,通配符“%”或“_”不要写在匹配字符串的左侧,参见以下两种书写方式:
select * from t where name like '_150_';
select * from t where name like 'a150_';
为test库t表的name列添加索引,索引名称idx_name:
mysql> create index idx_name on t(name);
mysql> show index from name;
对比方式一和方式二的执行效果:
mysql> select * from t where name like '_150_';
mysql> explain select * from t where name like '_150_';
mysql> select * from t where name like 'a150_';
mysql> explain select * from t where name like 'a150_';
结果如下:
从测试结果可以看出,使用like操作符的查询条件列带有索引时,如果通配符放在最左边,索引会失效,SQL优化器会选择效率低的全表扫解析方式,主要原因是对字符串类型创建索引时,MySQL将从最左开始选取一部分(767字符,最多到3072字符)字符串,将其内容存入到索引中。如果查询条件最左侧是可以匹配任意字符的通配符,无法定位具体的索引键值,优化器就会选择其他获取数据的方式而忽略索引的存在。因此,当带有索引的查询条件列是字符类型,如果使用模糊匹配操作符,不要将其放在最左侧,要放到第一个具体字符的右侧。
数据库怎么优化查询效率?