Python进阶----索引原理,mysql常见的索引,索引的使用,索引的优化,不能命中索引的情况,explain执行计划,慢查询和慢日志, 多表联查优化
一丶索引原理
什么是索引:
索引在MySQL中也称作'键',是存储引擎用于快速找到记录的一种数据结构.索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发更重要.
索引优化是对查询性能优化的手段,索引能够轻易将查询性能提高好几个量级.如果没有索引,则需要逐页去查询,可想而知效率就会低下
是否对索引产生误解:
索引是应用程序设计和开发的一个重要方面。若索引太多,应用程序的性能可能会受到影响。而索引太少,对查询性能又会产生影响,要找到一个平衡点,这对应用程序的性能至关重要。一些开发人员总是在事后才想起添加索引
索引原理:
通过不断地缩小想要获取数据的范围来少选出最终想要的结果,同时把随机的事件变成顺序的事件
磁盘IO与预读:
当一次IO时,不光把当前磁盘地址的数据读出,而且还把相邻的数据也读到内存缓冲区内.
当计算机再次访问一个地址的数据时,与其相邻的数据也会很快被访问到,每次IO读取的数据成为一页page,一页的数据量(block块)一般为4K或是8K,与操作系统设置有关,这个理论对于索引的数据结构设计非常有帮助
二丶索引的数据结构
数据结构的意义:
每次查询数据时把磁盘IO次数控制在一个很小的数量级
树:
树状图是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合.
特点:
1.每个节点有0个或多个子节点;
2.没有父节点的节点成为根节点
3.每一个非根节点有且只有一个父节点;
4.除了根节点外,每个子节点可以分为多个不相交的子树(子节点)
如图下图:?
根节点:A
父节点:A是B,C的父节点
叶子节点:D,E是叶子节点
树的深度/高度: 深度为 3
B+树
由二叉查找树进化成--->平衡二叉树(balance Tree)最终进化成--->B+树
b+树的性质:
1.索引字段要尽量的小
通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
2.索引的最左匹配特性
b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
3.数据只存储在叶子节点上 如: 数据 + 索引
4.在叶子节点上添加双向地址链接(链指针),更方便叶子节点之间进行数据的读取
三丶聚集索引和辅助索引
# 1. 在数据库中, B+树的高度一般都在2~4层,当查询某一个键值的行记录最多需要2~4次IO.
# 2. 聚集索引和辅助索引相同点:不管是聚集索引还是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子节点存放着所有的数据
# 3. 聚集索引和辅助索引的不通电:叶子节点存放的是否是一整行的信息
聚集索引(clustered index):
1.InnoDB存储引擎是索引组织表,即表中数据按照主键存放,由每张表的主键构成一颗B+树,同时叶子节点存放整张表的行记录数据,聚集索引的叶子节点也成为数据页. (数据 + 索引)
2.未定义主键,mysql默认自定义一个非空+唯一键(字段),作为聚集索引,但是这个字段不能被使用.
3.数据页只能是一颗B+树进行排序,因此每张表只拥有一个聚集索引
4.在多数情况下,查询优化器更倾向于采用聚集索引.因为每个聚集索引能够在B+树索引的叶子节点上直接获取找到数据,由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值查询
好处:
1.它对主键的排序查找和范围查找速度非常快,叶子节点的数据及时用户索要查询的数据.如用户需要查询一张表,查询最后10位用户信息,由于B+树索引是双向链表,所以用户可以快速找到最后一个数据页,并直接取出数据
### 没有添加 主键索引 前
mysql> select * from s1 order by id desc limit 10;
+---------+------+--------+-------------------+
| id | name | gender | email |
+---------+------+--------+-------------------+
| 2999999 | eva | female | eva2999999@oldboy |
| 2999998 | eva | female | eva2999998@oldboy |
| 2999997 | eva | female | eva2999997@oldboy |
| 2999996 | eva | female | eva2999996@oldboy |
| 2999995 | eva | female | eva2999995@oldboy |
| 2999994 | eva | female | eva2999994@oldboy |
| 2999993 | eva | female | eva2999993@oldboy |
| 2999992 | eva | female | eva2999992@oldboy |
| 2999991 | eva | female | eva2999991@oldboy |
| 2999990 | eva | female | eva2999990@oldboy |
+---------+------+--------+-------------------+
10 rows in set (3.93 sec) # 3秒多
# 添加主键
mysql> alter table s1 add primary key(id);
Query OK, 0 rows affected (44.24 sec)
Records: 0 Duplicates: 0 Warnings: 0
### 再次查询 ,添加主键后,不难发现运行速度快了
mysql> select * from s1 order by id desc limit 10;
+---------+------+--------+-------------------+
| id | name | gender | email |
+---------+------+--------+-------------------+
| 2999999 | eva | female | eva2999999@oldboy |
| 2999998 | eva | female | eva2999998@oldboy |
| 2999997 | eva | female | eva2999997@oldboy |
| 2999996 | eva | female | eva2999996@oldboy |
| 2999995 | eva | female | eva2999995@oldboy |
| 2999994 | eva | female | eva2999994@oldboy |
| 2999993 | eva | female | eva2999993@oldboy |
| 2999992 | eva | female | eva2999992@oldboy |
| 2999991 | eva | female | eva2999991@oldboy |
| 2999990 | eva | female | eva2999990@oldboy |
+---------+------+--------+-------------------+
10 rows in set (0.00 sec) # 毫秒级响应
2.范围查询(range query) 如果要查询主键某一范围内的数据,通过叶子节点的上层中间节点就可以获得到页的范围,可以直接读取数据
mysql> alter table s1 drop primary key;
Query OK, 2699998 rows affected (24.23 sec)
Records: 2699998 Duplicates: 0 Warnings: 0
mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | char(6) | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
rows in set (0.12 sec)
mysql> explain select * from s1 where id > 1 and id < 1000000; #没有聚集索引,预估需要检索的rows数如下
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 2690100 | 11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.00 sec)
mysql> alter table s1 add primary key(id);
Query OK, 0 rows affected (16.25 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from s1 where id > 1 and id < 1000000; #有聚集索引,预估需要检索的rows数如下
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1343355 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.09 sec)
辅助索引(secondary index):
1.除了聚集索引其他索引都是辅助索引,与聚集索引的区别是:辅助索引的叶子节点不包含行记录的全部数据
2.叶子节点出了包含键值意外,每个叶子节点中的索引还包含了一个书签(bookmark),该书签用来告诉innoDB存储引擎去哪里可以找到与索引相对应的行数据 (一行数据的某个字段的数据 如: age + 索引)
3.回表查询,拿到具体的索引,如果需要查询表中这个索引的其他数据,需要重新依据索引查询这一行的其他数据
4.每张表可以有多个辅助索引,但只能有一个聚集索引.当通过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并通过叶子级别的指针获得只想主键索引的主键,然后再通过主键索引来找到一个完整的行记录。
聚集索引和辅助索引的区别:
### 聚集索引
1.纪录的索引顺序与无力顺序相同
因此更适合between and和order by操作
2.叶子结点直接对应数据
从中间级的索引页的索引行直接对应数据页
3.每张表只能创建一个聚集索引
### 非聚集索引
1.索引顺序和物理顺序无关
2.叶子结点不直接指向数据页
3.每张表可以有多个非聚集索引,需要更多磁盘和内容
多个索引会影响insert和update的速度
四丶MySQL索引管理和常见索引
功能
1.索引的功能就是加快查找
2.mysql中的primary key ,unique ,联合唯一也都是索引,除了加速查找以外,还有约束功能
mysql常见的索引
### 普通索引INDEX : 加速查找 是 辅助索引
### 唯一索引:
# 主键索引 primary key : 加速查找+约束 (非空+不能重复) 非空 + 唯一 + 聚集索引
# 唯一索引 unique : 加速查找 + 约束 (不能重复) 唯一 + 辅助索引
### 联合索引
# primary key (id,name) : 联合主键索引
# unique (id ,name) : 联合唯一索引
# index (id , name ) : 联合普通索引
### 总结:?
# priamry key 的创建自带索引效果 非空 + 唯一 + 聚集索引
# unique 唯一约束的创建也自带索引效果 唯一 + 辅助索引
# index 普通的索引 辅助索引
索引的两大类型hash与btree
#我们可以在创建上述索引的时候,为其指定索引类型,分两类
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)
#不同的存储引擎支持的索引类型也不一样
InnoDB 支持事务,支持行级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事务,支持表级别锁定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引;
索引的创建和删除
### 创建
create index 索引名 on 表(字段);
### 删除
drop index 索引名 on 表
### 示例:
#方式一
create table t1(
id int,
name char,
age int ,
sex enum('male','female'),
unique key un_id(id),
index ix_name(name)
)
#方式二
create index ix_age on t1(age);
#方式三
alter table t1 add index ix_sex(sex);
alter table t1 add index(sex);
# 查看表结构
show create table t1;
mysql> show create table t1;
| t1 | CREATE TABLE `t1` (
`id` int(11) DEFAULT NULL,
`name` char(1) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` enum('male','female') DEFAULT NULL,
UNIQUE KEY `uni_id` (`id`),
KEY `ix_name` (`name`),
KEY `ix_age` (`age`),
KEY `ix_sex` (`sex`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
索引的优缺点
# 优点: 查询速度快
# 缺点: 1. 浪费空间,拖慢写的速度
# 2. 不要再程序中创建无用的索引
五丶索引测试
准备数据
#1. 准备表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);
#2. 创建存储过程,实现批量插入记录
delimiter $$ #声明存储过程的结束符号为$$
create procedure auto_insert1()
BEGIN
declare i int default 1;
while(i<3000000)do
insert into s1 values(i,'eva','female',concat('eva',i,'@oldboy'));
set i=i+1;
end while;
END$$ #$$结束
delimiter ; #重新声明分号为结束符号
#3. 查看存储过程
show create procedure auto_insert1\G
#4. 调用存储过程
call auto_insert1();
1.在没有索引的前提下测试查询速度
#无索引:mysql根本就不知道到底是否存在id等于333333333的记录,只能把数据表从头到尾扫描一遍,此时有多少个磁盘块就需要进行多少IO操作,所以查询速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)
2.在表中已经存在大量数据的前提下,为某个字段段建立索引,建立速度会很慢
3.在索引建立完毕后,以该字段为查询条件时,查询速度提升明显
注意:
1.mysql先去索引表里根据b+树的搜索原理很快搜索到id等于333333333的记录不存在,IO大大降低,因而速度明显提升**
2. 我们可以去mysql的data目录下找到该表,可以看到占用的硬盘空间多了
总结:
#1. 一定是为搜索条件的字段创建索引,比如select * from s1 where id = 333;就需要为id加上索引
#2. 在表中已经有大量数据的情况下,建索引会很慢,且占用硬盘空间,建完后查询速度加快
比如create index idx on s1(id);会扫描表中所有的数据,然后以id为数据项,创建索引结构,存放于硬盘的表中。
建完以后,再查询就会很快了。
#3. 需要注意的是:innodb表的索引会存放于s1.ibd文件中,而myisam表的索引则会有单独的索引文件table1.MYI
MySAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在innodb中,表数据文件本身就是按照B+Tree(BTree即Balance True)组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此innodb表数据文件本身就是主索引。
因为inndob的数据文件要按照主键聚集,所以innodb要求表必须要有主键(Myisam可以没有),如果没有显式定义,则mysql系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则mysql会自动为innodb表生成一个隐含字段作为主键,这字段的长度为6个字节,类型为长整型.
六丶正确使用索引
未命中索引的情况
# 1. 所查询的列不是创建了索引的列
# 2. 在条件中不能带运算或者函数,必须是"字段 = 值"
# 3. 如果创建索引的列的内容重复率高也不能有效利用索引(重复率不超过10%的列比较适合做索引)
# 4. 数据对应的范围如果太大的话,也不能有效利用索引(between and > < >= <= != not in)
# 5. like如果把%放在前面也不能命中索引
# 6. 多条件: and 只要有一列是索引列就可以名字, or 只有所有的条件列都是索引才会命中
# 7. 联合索引
# ①: 创建索引的顺序id,email,条件从哪一个字段开始出现范围,索引从那个位置失效了
# select * from s1 where id=1000000 and email like 'eva10000%' 命中索引
# select count(*) from s1 where id > 2000000 and email = 'eva2000000' 不能命中索引
# ②: 联合索引在使用的时候遵循最左前缀原则
select count(*) from s1 where email = 'eva2000000@oldboy';
# ③: 联合索引中只有使用 and 才能生效, 使用 or 不能生效
### 优化索引:
## 1. 创建表示建立好索引
## 2. 对常用的字段创建索引
## 3. 字段能够尽量的固定长度,就是固定长度
## 4. 使用联合索引
# ①: 多个条件相连的情况下,使用联合索引比单个字段索引要快
# ②: where a=xx and b=xxx;
# 对 a字段 和 b字段都创建索引 --联合索引
# create index ind_mix on s1(a,b)
# ③: 遵循最左左前缀原则(在条件中 , 索引字段尽量在不是索引字段前)
详细说明索引未命中
七丶联合索引和覆盖索引
联合索引
联合索引指的是表上的多个列合并起来做一个索引
覆盖索引
InnoDB存储引擎支持覆盖索引,即从辅助索引中就可以得到查询记录,则不需要查询聚集索引中的记录
# 覆盖索引 using index
# select count(id) from 表;
# select id from 表 where id <20;
# 索引合并
# 创建的时候是分开创建的
# 用的时候临时和在一起了
# using union 表示索引合并
八丶查询优化神奇-explain
1.查看sql语句的执行计划
2.explain select * from s1 where id<10000;
3.是否命中了索引,命中的索引的类型
### 执行计划:让mysql预估执行操作(一般正确)
all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const
id,email
慢:
select * from userinfo3 where name='alex'
explain select * from userinfo3 where name='alex'
# type: ALL(全表扫描)
select * from userinfo3 limit 1;
快:
select * from userinfo3 where email='alex'
# type: const(走索引)
九丶慢查询优化的基本步骤
0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
3.order by limit 形式的sql语句让排序的表优先查
4.了解业务方使用场景
5.加索引时参照建索引的几大原则
6.观察结果,不符合预期继续从0分析
十丶慢日志管理
# 慢日志是通过配置文件开启
# 如果数据库在你手里 你自己开
# 如果不在你手里 你也可以要求DBA帮你开
慢日志
- 执行时间 > 10
- 未命中索引
- 日志文件路径
配置:
- 内存
show variables like '%query%';
show variables like '%queries%';
set global 变量名 = 值
- 配置文件
mysqld --defaults-file='E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\my-default.ini'
my.conf内容:
slow_query_log = ON
slow_query_log_file = D:/....
注意:修改配置文件之后,需要重启服务
MySQL日志管理
========================================================
错误日志: 记录 MySQL 服务器启动、关闭及运行错误等信息
二进制日志: 又称binlog日志,以二进制文件的方式记录数据库中除 SELECT 以外的操作
查询日志: 记录查询的信息
慢查询日志: 记录执行时间超过指定时间的操作
中继日志: 备库将主库的二进制日志复制到自己的中继日志中,从而在本地进行重放
通用日志: 审计哪个账号、在哪个时段、做了哪些事件
事务日志或称redo日志: 记录Innodb事务相关的如事务执行时间、检查点等
========================================================
一、bin-log
1. 启用
# vim /etc/my.cnf
[mysqld]
log-bin[=dir\[filename]]
# service mysqld restart
2. 暂停
//仅当前会话
SET SQL_LOG_BIN=0;
SET SQL_LOG_BIN=1;
3. 查看
查看全部:
# mysqlbinlog mysql.000002
按时间:
# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56"
# mysqlbinlog mysql.000002 --stop-datetime="2012-12-05 11:02:54"
# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56" --stop-datetime="2012-12-05 11:02:54"
按字节数:
# mysqlbinlog mysql.000002 --start-position=260
# mysqlbinlog mysql.000002 --stop-position=260
# mysqlbinlog mysql.000002 --start-position=260 --stop-position=930
4. 截断bin-log(产生新的bin-log文件)
a. 重启mysql服务器
b. # mysql -uroot -p123 -e 'flush logs'
5. 删除bin-log文件
# mysql -uroot -p123 -e 'reset master'
二、查询日志
启用通用查询日志
# vim /etc/my.cnf
[mysqld]
log[=dir\[filename]]
# service mysqld restart
三、慢查询日志
启用慢查询日志
# vim /etc/my.cnf
[mysqld]
log-slow-queries[=dir\[filename]]
long_query_time=n
# service mysqld restart
MySQL 5.6:
slow-query-log=1
slow-query-log-file=slow.log
long_query_time=3 单位为秒
查看慢查询日志
测试:BENCHMARK(count,expr)
SELECT BENCHMARK(50000000,2*3);
十一丶7表联查优化
1.表结构优化
尽量用固定长度的数据类型替代可变的数据类型
把固定长度的字段放在前面
2.数据的角度上进行优化
如果列表中的数据越多,查询效率越低
列多:垂直分表
航多:水评分表
3.从sql的角度分说
1.尽力把条件写的细致些,where条件做筛选
2.多表尽量连表查询代替子查询
3.创建有效的索引,规避掉无效的索引
4.从配置角度上来说
开启慢日志查询,确认具体的有问题的sql,或者效率慢的sql
5.数据库
读写分离操作