一: 索引作用:
提供类似书中目录的作用,目的是为了优化查询
二:索引 的种类:
B树索引
Hash索引
R树
full text
GIS
三: B树基于不同的查找算法分类介绍
'''
B-tree:
B+tree 在范围查询方面提供了更好的性能(> < >= <=)
B*Tree
'''
四: 在功能上的分类
44.1 辅助索引(S)怎么构建B树结构的?
(1). 索引是基于表中,列(索引键)的值生成的B树结构
(2). 首先提取此列所有的值,进行自动排序
(3). 将排好序的值,均匀的分布到索引树的叶子节点中(16K)
(4). 然后生成此索引键值所对应得后端数据页的指针
(5). 生成枝节点和根节点,根据数据量级和索引键长度,生成合适的索引树高度
id name age gender
select * from t1 where id=10;
问题: 基于索引键做where查询,对于id列是顺序IO,但是对于其他列的查询,可能是随机IO.
alter table t1 add index idx(id)
4.2 聚集索引(C)
4.2.1 前提
(1)表中设置了主键,主键列就会自动被作为聚集索引.
(2)如果没有主键,会选择唯一键作为聚集索引.
(3)聚集索引必须在建表时才有意义,一般是表的无关列(ID)
4.2.2 辅助索引(S)怎么构建B树结构的?
(1) 在建表时,设置了主键列(ID)
(2) 在将来录入数据时,就会按照ID列的顺序存储到磁盘上.(我们又称之为聚集索引组织表)
(3) 将排好序的整行数据,生成叶子节点.可以理解为,磁盘的数据页就是叶子节点
4.2.3 聚集索引和辅助索引构成区别
聚集索引只能有一个,非空唯一,一般是主键
辅助索引,可以有多个,时配合聚集索引使用的
聚集索引叶子节点,就是磁盘的数据行存储的数据页
MySQL是根据聚集索引,组织存储数据,数据存储时就是按照聚集索引的顺序进行存储数据
辅助索引,只会提取索引键值,进行自动排序生成B树结构
五:辅助索引细分:
1:普通的单列辅助索引
2:联合索引: 多个列作为索引条件,生成索引树,理论上设计是好的,可以减少大量额回表查询
3:唯一索引: 索引的值都是唯一的。
六:关于索引树的高度受什么影响:
1:数据量级,解决方法:分表,分库,分布式
2:索引列值过长:解决方法; 前缀索引
3:数据类型: 变长长度字符串,使用了char, 解决方案:变长字符串使用varchar
enum类型的使用enum(’山东‘,’河北‘.....)
七:索引的基本管理
7.1 索引建立前
db01 [world]>desc city;
+-------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| Name | char(35) | NO | | | |
| CountryCode | char(3) | NO | MUL | | |
| District | char(20) | NO | | | |
| Population | int(11) | NO | | 0 | |
+-------------+----------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
Field :列名字
key :有没有索引,索引类型
PRI: 主键索引
UNI: 唯一索引
MUL: 辅助索引(单列,联和,前缀)
7.1 单列普通辅助索引
7.1.1 创建索引
alter table city add index idx_name(name);
create index idx_name1 oncity(name);
show indexfromcity;
alter table city drop index idx_name1; 删除索引
7.2 覆盖索引(联合索引)
lter table city add index idx_co_po(countrycode,population);
7.3 前缀索引:
alter table city add index idx_di(district(5));
注意:数字列不能用作前缀索引。
7.4 唯一索引
db01 [world]>alter table city add unique index idx_uni1(name);
ERROR 1062 (23000): Duplicate entry 'San Jose' for key 'idx_uni1'
selectdistrict,count(id)fromcitygroupby district;
需求:找到world下,city表中 name列有重复值的行,最后删掉重复的行
db01[world]>selectname,count(id)ascidfromcitygroupby name having cid>1order by cid desc;
db01[world]>select*fromcitywherename='suzhou';
通过存储过程 创建一张100w数据量的表(mysql函数):
use school;
create table t_100w(id int,num int, k1 char(2),k2 char(4),dt TIMESTAMP);
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),1),substring(str,1+floor(rand()*61),2));
set i=i+1;
insert into t_100w values(i, floor(rand()*num),str2,str4,NOW());
end while;
end;
call rand_data(1000000);
8:执行计划及获取
8.0 介绍:
(1)获取到的是优化器选择完成的,他认为代价最小的执行计划.作用:语句执行前,先看执行计划信息,可以有效的防止性能较差的语句带来的性能问题.如果业务中出现了慢语句,我们也需要借助此命令进行语句的评估,分析优化方案。
(2)select获取数据的方法
1.全表扫描(应当尽量避免,因为性能低)
2.索引扫描
3.获取不到数据
8.1 执行计划获取
获取优化器选择后的执行计划
desc select * from t_100w WHERE k2='780p';
explain select * from t_100w WHERE k2='780p';
两种获取方式都可以
结果:
主要关注点:
table: 查询的表
type: 查询的类型:全表, 索引
possible_keys: 可能会用到的索引
key: 使用到的索引
key_len: 应用索引的长度
rows: 查询结果集的长度
extra: 额外的信息
8.1.1: type 查询的类型:全表, 索引
ALL: 全表扫描,不走索引
有哪些情况:
在 where 语句后面查询 <>(不等于) , not in, like '%..' 在辅助索引中不走索引, 在聚集索引列还是会走索引
1:index: 全索引扫描
1:查询需要获取整个索引树种的值时:
desc select countcode from city;
2:联合索引中:任何一个非最左列作为查询条件时:
idx_a_b_c(a, b,c) ---> a, ab, abc
select * from t1 where b
use world;
desc city;
alter table city add index idx_c_p(countrycode, population);
desc select * FROM city where countrycode="CHN"
2: range: 索引范围扫描
辅助索引:> < >= <= like in or
主键索引: not in
1: desc select * from city where id<5;
3: ref: 非唯一性索引, 等值查询
desc select * from city where countrycode='chn'
4: eq_ref: 在多表连接时, 连接条件使用了唯一索引(uk, pk)
desc select b.name, a.name from city as a join country as b on a.countrycode=b.code where a.population <100;
5:system, const 唯一索引的等值查询
desc select * from city where id=10;
索引性能递增
8.2.2 extra: filesort, 文件排序
desc select * from city where countrycode='chn' order by population;
ALTER TABLE city ADD INDEX idx_c_p(countrycode,population);
desc select * from city where countrycode='chn' order by population;
结论:
1.当我们看到执行计划extra位置出现filesort,说明由文件排序出现
2.观察需要排序(ORDER BY,GROUP BY ,DISTINCT )的条件,有没有索引
3. 根据子句的执行顺序,去创建联合索引
8.2.3 explain(desc)使用场景(面试题)
题目意思: 我们公司业务慢,请你从数据库的角度分析原因
1.mysql出现性能问题,我总结有两种情况:
(1)应急性的慢:突然夯住
应急情况:数据库hang(卡了,资源耗尽)
处理过程:
1.show processlist; 获取到导致数据库hang的语句
2. explain 分析SQL的执行计划,有没有走索引,索引的类型情况
3. 建索引,改语句
(2)一段时间慢(持续性的):
(1)记录慢日志slowlog,分析slowlog
(2)explain 分析SQL的执行计划,有没有走索引,索引的类型情况
(3)建索引,改语句
9. 索引应用规范
9.1 建立索引的原则(DBA运维规范)
9.1.0 说明
为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索引和创建什么类型的索引。那么索引设计原则又是怎样的?
9.1.1 (必须的) 建表时一定要有主键,一般是个无关列
9.1.2 选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。
如果使用姓名的话,可能存在同名现象,从而降低查询速度。
9.1.3(必须的) 为经常需要where 、ORDER BY、GROUP BY,join on等操作的字段
为其建立索引,优化查询
注:如果经常作为条件的列,重复值特别多,可以建立联合索引
9.1.4 尽量使用前缀来索引:
如果索引字段的值很长,最好使用值的前缀来索引。
9.1.5 限制索引的数目
索引的数目不是越多越好。
可能会产生的问题:
(1) 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
(2) 修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
(3) 优化器的负担会很重,有可能会影响到优化器的选择.
percona-toolkit中有个工具,专门分析索引是否有用
9.1.6 删除不再使用或者很少使用的索引(percona toolkit)
pt-duplicate-key-checker
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理
员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
9.1.7 大表加索引,要在业务不繁忙期间操作
9.1.8 尽量少在经常更新值的列上建索引
9.1.9 建索引原则
(1)必须要有主键,如果没有可以做为主键条件的列,创建无关列
(2)经常做为where条件列 order bygroupbyjoinon,distinct 的条件(业务:产品功能+用户行为)
(3)最好使用唯一值多的列作为索引,如果索引列重复值较多,可以考虑使用联合索引
(4)列值长度较长的索引列,我们建议使用前缀索引.
(5)降低索引条目,一方面不要创建没用索引,不常使用的索引清理,perconatoolkit(xxxxx)
(6)索引维护要避开业务繁忙期
9.2 不走索引的情况(开发规范)
9.2.1 没有查询条件,或者查询条件没有建立索引
select * from tab; 全表扫描。
select * from tab where 1=1;
在业务数据库中,特别是数据量比较大的表。
是没有全表扫描这种需求。
1、对用户查看是非常痛苦的。
2、对服务器来讲毁灭性的。
(1)
select * from tab;
SQL改写成以下语句:
select * from tab order by price limit 10 ; 需要在price列上建立索引
(2)
select * from tab where name='zhangsan' name列没有索引
改:
1、换成有索引的列作为查询条件
2、将name列建立索引
9.2.2 查询结果集是原表中的大部分数据,应该是25%以上。
查询的结果集,超过了总数行数25%,优化器觉得就没有必要走索引了。
假如:tab表 id,name id:1-100w ,id列有(辅助)索引
select * from tab where id>500000;
如果业务允许,可以使用limit控制。
怎么改写 ?
结合业务判断,有没有更好的方式。如果没有更好的改写方案
尽量不要在mysql存放这个数据了。放到redis里面。
9.2.3 索引本身失效,统计数据不真实
索引有自我维护的能力。
对于表内容变化比较频繁的情况下,有可能会出现索引失效。
一般是删除重建
现象:
有一条select语句平常查询时很快,突然有一天很慢,会是什么原因
select? --->索引失效,,统计数据不真实
DML ? --->锁冲突
9.2.4 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)
例子:
错误的例子:select * from test where id-1=9;
正确的例子:select * from test where id=10;
算术运算
函数运算
子查询
9.2.5 隐式转换导致索引失效.这一点应当引起重视.也是开发中经常会犯的错误.
这样会导致索引失效. 错误的例子:
mysql> alter table tab add index inx_tel(telnum);
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql>
mysql> desc tab;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| telnum | varchar(20) | YES | MUL | NULL | |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> select * from tab where telnum='1333333';
+------+------+---------+
| id | name | telnum |
+------+------+---------+
| 1 | a | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> select * from tab where telnum=1333333;
+------+------+---------+
| id | name | telnum |
+------+------+---------+
| 1 | a | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum='1333333';
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| 1 | SIMPLE | tab | ref | inx_tel | inx_tel | 63 | const | 1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum=1333333;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | tab | ALL | inx_tel | NULL | NULL | NULL | 2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum=1555555;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | tab | ALL | inx_tel | NULL | NULL | NULL | 2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum='1555555';
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| 1 | SIMPLE | tab | ref | inx_tel | inx_tel | 63 | const | 1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql>
9.2.6 <> ,not in 不走索引(辅助索引)
EXPLAIN SELECT * FROM teltab WHERE telnum <> '110';
EXPLAIN SELECT * FROM teltab WHERE telnum NOT IN ('110','119');
mysql> select * from tab where telnum <> '1555555';
+------+------+---------+
| id | name | telnum |
+------+------+---------+
| 1 | a | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum <> '1555555';
单独的>,<,in 有可能走,也有可能不走,和结果集有关,尽量结合业务添加limit
or或in 尽量改成union
EXPLAIN SELECT * FROM teltab WHERE telnum IN ('110','119');
改写成:
EXPLAIN SELECT * FROM teltab WHERE telnum='110'
UNION ALL
SELECT * FROM teltab WHERE telnum='119'
9.2.7 like "%_" 百分号在最前面不走
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%' 走range索引扫描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110' 不走索引
%linux%类的搜索需求,可以使用elasticsearch+mongodb 专门做搜索服务的数据库产品