索引
存储过程生成100W数据
1.创建数据库oldboy
mysql> create database oldboy;
2.创建存储过程,并且创建100W条数据use oldboy
use oldboy
create table t100w (id int,num int,k1 char(2),k2 char(4),dt timestamp) charset utf8mb4 collate utf8mb4_bin;
delimiter //
create procedure rand_data(in num int)
begin
declare str char(62) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
declare str2 char(2);
declare str4 char(4);
declare i int default 0;
while i<num do
set str2=concat(substring(str,1+floor(rand()*61),1),substring(str,1+floor(rand()*61),1));
set str4=concat(substring(str,1+floor(rand()*61),2),substring(str,1+floor(rand()*61),2));
set i=i+1;
insert into t100w values (i,floor(rand()*num),str2,str4,now());
end while;
end;
//
delimiter ;
插入100w条数据:
call rand_data(1000000);
commit;
压力测试
[root@localhost ~]# mysqlslap --defaults-file=/etc/my.cnf \
> --concurrency=100 --iterations=1 --create-schema='oldboy' \
> --query="select * from oldboy.t100w where k2='xyMN'" engine=innodb \
> --number-of-queries=2000 -uroot -p123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
Running for engine rbose
Average number of seconds to run all queries: 339.064 seconds
Minimum number of seconds to run all queries: 339.064 seconds
Maximum number of seconds to run all queries: 339.064 seconds
Number of clients running queries: 100
Average number of queries per client: 20
索引的作用
提供类似于书中目录的作用,目的是为了优化查询
索引的种类(算法)
- B树索引
- Hash索引
- R树索引
- Full text
- GIS
B树索引的算法
Mysql现在用的是B*Tree。
辅助索引(S)构建B树结构
Secondary Index
(1)辅助索引是基于表的列进行生成的
(2)取出索引列的所有值(取出所有键值),临时表中存储
(3)进行所有键值的排序
(4)将所有的键值按顺序落到BTree索引的叶子节点上
(5)进而生成枝节点和根节点
(6)叶子节点除了存储键值之外,还存储了相邻叶子节点的指针,另外还会保存原表数据的指针
聚集索引(C)构建B树结构
Cluster Index
(1) 建表时有主键列(ID)
(2) 表中进行数据存储,会按照ID列的顺序,有序的存储一行一行的数据到数据页上(这个动作叫做聚集索引组织表)
(3) 表的数据页被作为聚集索引的叶子节点
(4) 把叶子节点的主键值生成上层枝节点和根节点。
聚集索引和辅助索引构成区别总结
聚集索引只能有一个,非空唯一,一般时主键
辅助索引,可以有多个,是配合聚集索引使用的
聚集索引叶子节点,就是磁盘的数据行存储的数据页
MySQL是根据聚集索引,组织存储数据,数据存储时就是按照聚集索引的顺序进行存储数据
辅助索引,只会提取索引键值,进行自动排序生成B树结构
辅助索引细分
- 联合多列辅助索引(覆盖索引)
- 单列的辅助索引
- 唯一索引
索引树的高度
关于索引树的高度受什么影响?
(1)数据行多, 分表
(2)索引列字符长度 ,前缀索引
(3)char varchar ,表设计
(4)enum 优化索引高度,能用则用。
索引的管理
辅助索引:单列的辅助索引
# 查看t100w表的结构
mysql> desc t100w;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------+------+-----+-------------------+-----------------------------+
| id | int(11) | YES | | NULL | |
| num | int(11) | YES | | NULL | |
| k1 | char(2) | YES | | NULL | |
| k2 | char(4) | YES | | NULL | |
| dt | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
5 rows in set (0.00 sec)
# 查看t100w表在系统中占用的空间 (查询条件为k2)
[root@localhost ~]# ll -h /data/mysql/data/oldboy/
total 57M
-rw-r-----. 1 mysql mysql 65 Apr 28 22:03 db.opt
-rw-r-----. 1 mysql mysql 8.5K Apr 28 22:06 t100w.frm
-rw-r-----. 1 mysql mysql 56M Apr 28 22:24 t100w.ibd
# 并发100,2000次查询消耗时间 339.064 seconds
[root@localhost ~]# mysqlslap --defaults-file=/etc/my.cnf \
--concurrency=100 --iterations=1 --create-schema='oldboy' \
--query="select * from oldboy.t100w where k2='xyMN'" engine=innodb \
--number-of-queries=2000 -uroot -p123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
Running for engine rbose
Average number of seconds to run all queries: 339.064 seconds
Minimum number of seconds to run all queries: 339.064 seconds
Maximum number of seconds to run all queries: 339.064 seconds
Number of clients running queries: 100
Average number of queries per client: 20
# 建立索引 (会锁表,短暂的锁表,最好在业务不繁忙的时候建立索引)
mysql> alter table t100w add index idx_k2(k2);
Query OK, 0 rows affected (1.41 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 再次进行压测
[root@localhost ~]# mysqlslap --defaults-file=/etc/my.cnf \
> --concurrency=100 --iterations=1 --create-schema='oldboy' \
> --query="select * from oldboy.t100w where k2='xyMN'" engine=innodb \
> --number-of-queries=2000 -uroot -p123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
Running for engine rbose
Average number of seconds to run all queries: 0.488 seconds
Minimum number of seconds to run all queries: 0.488 seconds
Maximum number of seconds to run all queries: 0.488 seconds
Number of clients running queries: 100
Average number of queries per client: 20
# 优化效果大约在800倍左右
有无索引查询效果很明显
# 查看表中的索引(MUL是辅助索引)
mysql> desc t100w;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------+------+-----+-------------------+-----------------------------+
| id | int(11) | YES | | NULL | |
| num | int(11) | YES | | NULL | |
| k1 | char(2) | YES | | NULL | |
| k2 | char(4) | YES | MUL | NULL | |
| dt | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
5 rows in set (0.00 sec)
# 更详细的查看表中的索引
mysql> show index from t100w\G
*************************** 1. row ***************************
Table: t100w
Non_unique: 1
Key_name: idx_k2
Seq_in_index: 1
Column_name: k2
Collation: A
Cardinality: 3880
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
1 row in set (0.00 sec)
辅助索引:唯一索引
列中的数据不能重复
# 判断想建索引的列中有没有重复的数据:使用distinct,对比两次的查询行数是否一致。
mysql> select count(k1) from t100w;
+-----------+
| count(k1) |
+-----------+
| 1000000 |
+-----------+
1 row in set (0.29 sec)
mysql> select count(distinct(k1)) from t100w;
+---------------------+
| count(distinct(k1)) |
+---------------------+
| 3721 |
+---------------------+
1 row in set (0.58 sec)
# 当然,分组统计也可以,大于1的就代表重复。
mysql> select k1,count(k1) from t100w group by k1 having count(k1)>1 order by count(k1) desc limit 5;
+------+-----------+
| k1 | count(k1) |
+------+-----------+
| OH | 328 |
| K4 | 326 |
| A3 | 322 |
| kd | 319 |
| N6 | 318 |
+------+-----------+
5 rows in set (0.56 sec)
# 其实直接创建唯一索引也行,因为如果有重复的行,创建会失败,哈哈。
mysql> alter table t100w add unique index idx_k1(k1);
ERROR 1062 (23000): Duplicate entry 'pi' for key 'idx_k1'
辅助索引:前缀索引
# 建立前缀索引:前5个字符(从左到右)
mysql> alter table city add index idx_name(name(5));
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
辅助索引:联合索引
(1) where A、GROUP BY B、ORDER BY C ---> (A,B,C)
(2) where A B C
(2.1) 都是等值 ,在5.5 以后无关索引顺序,创建索引的原则:唯一值多的列放在联合索引的最左侧
(2.2) 如果有不等值,例如以下情况
select where A= and B> and C=
索引顺序,ACB ,语句改写为 ACB
# 创建联合索引
mysql> alter table city add index idx_co_po(countrycode,population);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
删除索引
# 删除city表中的idx_co_po索引
mysql> alter table city drop index idx_co_po;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
执行计划
作用
- 上线新的查询语句之前,进行提前预估语句的性能
- 在出现性能问题时,找到合理的解决思路
获取执行计划(desc = explain 功能一样)
mysql> desc select * from oldboy.t100w where k2='EF12'\G
mysql> explain select * from oldboy.t100w where k2='EF12'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t100w
partitions: NULL
type: ref
possible_keys: idx_k2
key: idx_k2
key_len: 17
ref: const
rows: 257
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
# 解析
table: t100w # 多表查询的时候,特别有用
type: ref # 索引的应用级别
possible_keys: idx_k2 # 可能会使用到的索引
key: idx_k2 # 实际上使用的索引
key_len: 17 # 联合索引覆盖长度
rows: 293 # 查询的行数(越少越好)
Extra: NULL # 额外的信息
type
索引的应用级别
ALL : 全表扫描,不走索引
# 没建立索引!!
# 建立索引不走的()!!!!
mysql> desc select * from t100w;
mysql> desc select * from t100w where k1='aa'; # k2做的索引列,拿k1去查询,当然走不了索引
mysql> desc select * from t100w where k2 != 'aaaa'; # 对于辅助索引来说,不等于是不走索引的
mysql> desc select * from t100w where k2 like '%xt%'; # 模糊查找不走索引。 除非'xt%'才走索引。
Index :全索引扫描
mysql> desc select k2 from t100w;
range :索引范围扫描
# 辅助索引 : > < >= <= like , in or
mysql> desc select * from world.city where countrycode like 'C%'
mysql> desc select * from world.city where countrycode in ('CHN','USA');
改写为:
desc
select * from world.city where countrycode='CHN'
union all
select * from world.city where countrycode='USA';
# 主键: !=
mysql> desc select * from world.city where id!=3000;
mysql> desc select * from world.city where id>3000;
ref : 辅助索引等值查询
mysql> desc select * from city where countrycode='CHN'
eq_ref :在多表连接查询是on的条件列是唯一索引或主键
# 左边 ALL 右边 eq_ref
mysql> desc select a.name,b.name ,b.surfacearea
from city as a
join country as b
on a.countrycode=b.code
where a.population <100;
const,system : 主键或唯一键等值查询
mysql> DESC SELECT * from city where id=10
null:查询不到数据的时候,就是性能最好的时候
mysql> desc select * from city where id=1000000000;
Extra
额外信息
# Using filesort
mysql> desc select * from city where countrycode="CHN" order by population\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: city
partitions: NULL
type: ref
possible_keys: CountryCode
key: CountryCode
key_len: 12
ref: const
rows: 363
filtered: 100.00
Extra: Using index condition; Using filesort
1 row in set, 1 warning (0.00 sec)
# where 和 orderby 同时出现需要使用联合索引进行优化
mysql> alter table city add index idx_co_po(countrycode,population);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 再次查询发现 Extra 中已经没有 Using filesort
mysql> desc select * from city where countrycode="CHN" order by population\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: city
partitions: NULL
type: ref
possible_keys: CountryCode,idx_co_po
key: idx_co_po
key_len: 12
ref: const
rows: 363
filtered: 100.00
Extra: Using index condition
1 row in set, 1 warning (0.00 sec)
explain(desc)使用场景
公司业务慢,请你从数据库的角度分析原因:
(1)应急性的慢:突然夯住
处理过程:
1.show processlist; 获取到导致数据库hang的语句
2. explain 分析SQL的执行计划,有没有走索引,索引的类型情况
3. 建索引,改语句
(2)一段时间慢(持续性的):
1. 记录慢日志slowlog,分析slowlog,获取到导致数据库slow的语句
2. explain 分析SQL的执行计划,有没有走索引,索引的类型情况
3. 建索引,改语句
索引管理原则
为了使索哪些引的使用效率更高,在创建索引时,必须考虑在字段上创建索引和创建什么类型的索引。
-
建表时一定要有主键,一般是个无关列
-
选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。 例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。 如果使用姓名的话,可能存在同名现象,从而降低查询速度。 优化方案: (1) 如果非得使用重复值较多的列作为查询条件(例如:男女),可以将表逻辑拆分 (2) 可以将此列和其他的查询类,做联和索引 select count(*) from world.city; select count(distinct countrycode) from world.city; select count(distinct countrycode,population ) from world.city;
-
为经常需要where 、ORDER BY、GROUP BY,join on等操作的字段是创建索引,排序操作会浪费很多时间。
where A B C ----》 A B C
in
where A group by B order by C
A,B,C
where A、GROUP BY B、ORDER BY C
1. 联合索引
2. (A,B,C)
如果为其建立索引,优化查询
注:如果经常作为条件的列,重复值特别多,可以建立联合索引。
- 尽量使用前缀来索引:如果索引字段的值很长,最好使用值的前缀来索引。
- 限制索引的数目:索引的数目不是越多越好。
可能会产生的问题: percona-toolkit中有个工具,专门分析索引是否有用
(1) 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
(2) 修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
(3) 优化器的负担会很重,有可能会影响到优化器的选择.
- 删除不再使用或者很少使用的索引(percona toolkit):pt-duplicate-key-checker
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理
员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
-
大表加索引,要在业务不繁忙期间操作
-
尽量少在经常更新值的列上建索引
不走索引的情况
- 没有查询条件,或者查询条件没有建立索引
select * from tab; 全表扫描。
select * from tab where 1=1;
-
查询结果集是原表中的大部分数据,应该是25%以上。(查询的结果集,超过了总数行数25%,优化器觉得就没有必要走索引了。)
-
索引本身失效,统计数据不真实
索引有自我维护的能力。
对于表内容变化比较频繁的情况下,有可能会出现索引失效。
一般是删除重建
现象:
有一条select语句平常查询时很快,突然有一天很慢,会是什么原因
select? --->索引失效,,统计数据不真实
DML ? --->锁冲突
- 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)
错误的例子:select * from test where id-1=9;
正确的例子:select * from test where id=10;
算术运算
函数运算
子查询
-
隐式转换导致索引失效.
-
<> ,not in 不走索引(辅助索引)
-
like "%_" 百分号在最前面不走
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%' 走range索引扫描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110' 不走索引
%linux%类的搜索需求,可以使用elasticsearch+mongodb 专门做搜索服务的数据库产品