6.1索引的基本概念
#什么是索引?
一个在存储表阶段就有的一个存储结构,能够在查询(读操作)时加速#索引的本质
通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
#索引的重要性
读写比例 10:1,读(查询)的速度至关重要#索引虽然提高了检测效率,但不能随意添加
原因:索引也是数据库当中的对象,也需要数据库不断维护#当表中数据需要经常更改,就不适合做索引
原因:数据一旦修改,索引需要重新排序维护
6.1.1block 磁盘预读原理
linux操作系统中一次性会读取4096个字节(一个block块)
前面提到了访问磁盘,那么这里先简单介绍一下磁盘IO和预读,磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS(Million Instructions Per Second)的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行约450万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。下图是计算机硬件延迟的对比图,供大家参考:
读硬盘的io操作的时间非常的长,比cpu执行指令的时间长很多
所以尽量的减少IO次数才是读写数据的主要解决的问题
6.2索引的数据结构
6.2.1树
树状图是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
它具有以下的特点:每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树
示例:
根结点 : A
父节点 : A是B,C的父节点
叶子节点:D,E是叶子节点
树的深度/树的高度:高度为3
6.2.2b树
b树英文balance tree,又称平衡树
总结 :加索引,加读取速度,但写的速度慢
6.2.3B+树
在b树的基础上进行了改良
1.分支接点和根节点都不在存储实际数据 ,而是将所有的实际数据都存在于叶子节点中,导致分支和根节点能存储更多的索引信息,降低了树的高度
2.在叶子节点之间加入双向的链式结构 ,方便在查询中的范围条件
#注意:#mysql当中的所有b+树索引的高度都基本控制在3层
1.io操作的次数非常稳定2.有利于通过范围查询
#什么会影响索引效率 ------树的高度
1.对哪一列创建索引,尽量选择短的列做索引2.对区分度高的列建索引,重复率超过了10%就不适合创建索引
#b+树性质
1.索引字段要尽量的小2.索引的最左匹配特性
6.3聚集索引和辅助索引
在数据库中,B+树的高度一般都在2~4层,这也就是说查找某一个键值的行记录时最多只需要2到4次IO,这倒不错。因为当前一般的机械硬盘每秒至少可以做100次IO,2~4次的IO意味着查询时间只需要0.02~0.04秒。
数据库中的B+树索引可以分为聚集索引(clustered index)和辅助索引(secondary index),
聚集索引与辅助索引相同的是:不管是聚集索引还是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子结点存放着所有的数据。
聚集索引与辅助索引不同的是:叶子结点存放的是否是一整行的信息
6.3.1聚集索引
#InnoDB存储引擎表是索引组织表,即表中数据按照主键顺序存放。
而聚集索引(clustered index)就是按照每张表的主键构造一棵B+树,同时叶子结点存放的即为整张表的行记录数据,也将聚集索引的叶子结点称为数据页。
聚集索引的这个特性决定了索引组织表中数据也是索引的一部分。同B+树数据结构一样,每个数据页都通过一个双向链表来进行链接。#如果未定义主键,MySQL取第一个唯一索引(unique)而且只含非空列(NOT NULL)作为主键,InnoDB使用它作为聚簇索引。
#如果没有这样的列,InnoDB就自己产生一个这样的ID值,它有六个字节,而且是隐藏的,使其作为聚簇索引。
#由于实际的数据页只能按照一棵B+树进行排序,因此每张表只能拥有一个聚集索引。
在多数情况下,查询优化器倾向于采用聚集索引。因为聚集索引能够在B+树索引的叶子节点上直接找到数据。此外由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值得查询。
聚集索引的好处之一:它对主键的排序查找和范围查找速度非常快,叶子节点的数据就是用户所要查询的数据。如用户需要查找一张表,查询最后的10位用户信息,由于B+树索引是双向链表,所以用户可以快速找到最后一个数据页,并取出10条记录
6.3.2辅助索引
除了聚集索引外其他索引都是辅助索引(Secondary Index,也称为非聚集索引),辅助索引的叶子节点不包含行记录的全部数据。
叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含一个书签(bookmark)。该书签用来告诉InnoDB存储引擎去哪里可以找到与索引相对应的行数据。
辅助索引的存在并不影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引,但只能有一个聚集索引。当通过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并通过叶子级别的指针获得主键索引的主键,然后再通过主键索引来找到一个完整的行记录。
#聚集索引
1.纪录的索引顺序与无力顺序相同
因此更适合between and和order by操作2.叶子结点直接对应数据
从中间级的索引页的索引行直接对应数据页3.每张表只能创建一个聚集索引
#非聚集索引
1.索引顺序和物理顺序无关2.叶子结点不直接指向数据页3.每张表可以有多个非聚集索引,需要更多磁盘和内容
多个索引会影响insert和update的速度#innodb
聚集索引(主键) 和 辅助索引(除主键之外所有索引)共存#myisam
只有辅助索引,没有聚集索引
6.4MySQL索引管理
6.4.1功能
#1. 索引的功能就是加速查找#2. mysql中的primary key,unique,联合唯一也都是索引,这些索引除了加速查找以外,还有约束的功能
6.4.2MySQL常用的索引
#普通索引:
INDEX:加速查找
#唯一索引:
-主键索引PRIMARY KEY:加速查找+约束(不为空、不能重复)-唯一索引UNIQUE:加速查找+约束(不能重复)
#联合索引:
-PRIMARY KEY(id,name):联合主键索引-UNIQUE(id,name):联合唯一索引-INDEX(id,name):联合普通索引
各个索引的应用场景
举个例子来说,比如你在为某商场做一个会员卡的系统。#这个系统有一个会员表,有下列字段:
会员编号 INT
会员姓名 VARCHAR(10)
会员身份证号码 VARCHAR(18)
会员电话 VARCHAR(10)
会员住址 VARCHAR(50)
会员备注信息 TEXT
那么这个 会员编号,作为主键,使用 PRIMARY
会员姓名 如果要建索引的话,那么就是普通的 INDEX
会员身份证号码 如果要建索引的话,那么可以选择 UNIQUE (唯一的,不允许重复)
#除此之外还有全文索引,即FULLTEXT
会员备注信息 , 如果需要建索引的话,可以选择全文搜索。
用于搜索很长一篇文章的时候,效果最好。
用在比较短的文本,如果就一两行字的,普通的 INDEX 也可以。
但其实对于全文搜索,我们并不会使用MySQL自带的该索引,而是会选择第三方软件如Sphinx,专门来做全文搜索。
#其他的如空间索引SPATIAL,了解即可,几乎不用
6.4.3索引的两大类型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 等索引;
6.4.4创建/删除索引
1 #方法一:创建表时
2 CREATE TABLE 表名 (3 字段名1 数据类型 [完整性约束条件…],4 字段名2 数据类型 [完整性约束条件…],5 [UNIQUE | FULLTEXT | SPATIAL ] INDEX |KEY6 [索引名] (字段名[(长度)] [ASC |DESC])7 );8
9
10 #方法二:CREATE在已存在的表上创建索引
11 CREATE [UNIQUE | FULLTEXT |SPATIAL ] INDEX 索引名12 ON 表名 (字段名[(长度)] [ASC |DESC]) ;13
14
15 #方法三:ALTER TABLE在已存在的表上创建索引
16 ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT |SPATIAL ] INDEX17 索引名 (字段名[(长度)] [ASC |DESC]) ;18
19 #删除索引:DROP INDEX 索引名 ON 表名字;
20 #方式一
21 create table t1(22 id int,23 name char,24 age int,25 sex enum('male','female'),26 unique key uni_id(id),27 index ix_name(name) #index没有key
28 );29 create table t1(30 id int,31 name char,32 age int,33 sex enum('male','female'),34 unique key uni_id(id),35 index(name) #index没有key
36 );37 38 39 #方式二
40 create index ix_age on t1(age);41 42 43 #方式三
44 alter table t1 add index ix_sex(sex);45 alter table t1 add index(sex);46 47 #查看
48 mysql>show create table t1;49 | t1 |CREATE TABLE `t1` (50 `id` int(11) DEFAULT NULL,51 `name` char(1) DEFAULT NULL,52 `age` int(11) DEFAULT NULL,53 `sex` enum('male','female') DEFAULT NULL,54 UNIQUE KEY `uni_id` (`id`),55 KEY `ix_name` (`name`),56 KEY `ix_age` (`age`),57 KEY `ix_sex` (`sex`)58 ) ENGINE=InnoDB DEFAULT CHARSET=latin1
View Code
6.5测试索引
6.5.1数据准备
#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 default1;while(i<3000000)do
insert into s1 values(i,'eva','female',concat('eva',i,'@oldboy'));
set i=i+1;
endwhile;
END$$#$$结束
delimiter ; #重新声明分号为结束符号
#3. 查看存储过程
show create procedure auto_insert1\G#4. 调用存储过程
call auto_insert1();
View Code
6.5.2数据的测试
在没有索引的前提下测试查询速度
#无索引:mysql根本就不知道到底是否存在id等于333333333的记录,只能把数据表从头到尾扫描一遍,此时有多少个磁盘块就需要进行多少IO操作,所以查询速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)
在表中已经存在大量数据的前提下,为某个字段段建立索引,建立速度会很慢
在索引建立完毕后,以该字段为查询条件时,查询速度提升明显
PS:
1. mysql先去索引表里根据b+树的搜索原理很快搜索到id等于333333333的记录不存在,IO大大降低,因而速度明显提升
2. 我们可以去mysql的data目录下找到该表,可以看到占用的硬盘空间多了
3. 需要注意,如下图
**
总结
#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个字节,类型为长整型.
View Code
6.6索引不生效的原因
#总结:#单列索引
#选择一个区分度高的列建立索引,条件中的列不要参与计算,条件的范围尽量小,使用and作为条件的连接符#使用or来连接多个条件
#在满足上述条件的基础上
#对or相关的所有列分别创建索引
原因一:索引列不能在条件中参与计算
原因二: 要查询的数值范围大(范围越小,速度越快io操作少)
!=,>,=,<=
between and
like
结果的范围大,索引不生效
如果 abc% 索引生效, %abc索引就不生效
原因三:一列内容的区分度不高(使用name列测试)
原因四 :对两列内容进行条件查询 and or
先去掉id列索引,--> id无索引,email有索引
and
and条件两端的内容,优先选一个有索引的,并且树形结构更好的来进行查询
只有两个条件都成立才能完成where条件,先生成范围小的,缩小后面条件的压力
select * from s1 where id = 1000000 and email = 'eva1000000@oldboy';
or
or条件的,不会进行优化,只是根据条件从左到右依次进行筛选
条件中带有or的要想命中索引列,这些条件中所有的列都是索引列
select * from s1 where id = 1000000 and email = 'eva1000000@oldboy';
原因五: 联合索引
在联合索引中如果使用了or条件,索引不生效
最左前缀原则 :在联合索引中,条件必须含有在创建索引时的第一个索引列
在整个条件中,从开始出现模糊匹配的那一刻,索引就失效了
为避免其他索引干扰,删掉其他索引
创建联合索引
create index ind_mix on s1(id,email);
or条件示例
最左前缀原则示例
其他情况
#使用函数
select * from tb1 where reverse(email) = 'egon';#类型不一致
如果列是字符串类型,传入条件是必须用引号引起来,不然...
select* from tb1 where email = 999;#排序条件为索引,则select字段必须也是索引字段,否则无法命中
-order by
select namefroms1 order by email desc;
当根据索引排序时候,select查询的字段如果不是索引,则速度仍然很慢
select emailfroms1 order by email desc;
特别的:如果对主键排序,则还是速度很快:
select* fromtb1 order by nid desc;#组合索引最左前缀
如果组合索引为:(name,email)
nameand email --命中索引
name--命中索引
email--未命中索引
- count(1)或count(列)代替count(*)在mysql中没有差别了- create index xxxx on tb(title(19)) #text类型,必须制定长度
其他注意事项
1.避免使用select *
2.使用count(*)3.创建表时尽量使用 char 代替 varchar4.表的字段顺序固定长度的字段优先5.组合索引代替多个单列索引(由于mysql中每次只能使用一个索引,所以经常使用多个条件查询时更适合使用组合索引)6.尽量使用短索引7.使用连接(JOIN)来代替子查询(Sub-Queries)8.连表时注意条件类型需一致9.索引散列值(重复少)不适合建索引,例:性别不适合
6.7合并索引和覆盖索引
6.7.1覆盖索引
InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖),即从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录。
注意:覆盖索引技术最早是在InnoDB Plugin中完成并实现,这意味着对于InnoDB版本小于1.0的,或者MySQL数据库版本为5.0以下的,InnoDB存储引擎不支持覆盖索引特性
6.7.2合并索引
联合索引是指对表上的多个列合起来做一个索引。联合索引的创建方法与单个索引的创建方法一样,不同之处仅在于有多个索引列
mysql>create table t(->a int,->b int,->primary key(a),->key idx_a_b(a,b)->);
Query OK, 0 rows affected (0.11 sec)
联合索引的第二个好处是在第一个键相同的情况下,已经对第二个键进行了排序处理
对两个字段分别创建索引,由于sql的条件让两个索引同时生效,这两个索引就是合并索引
6.8查询优化神器-explain
explain select id from s1 where id = 1000000;
explain select count(id)from s1 where id > 1000000;#执行计划 :explain
#如果想在执行sql之前就知道sql语句的执行情况,可以使用执行计划
#语法:
explain select id from s1 where id = 1000000;#情况一:
30000000条数据,假设查一条数据要20秒,不想用这么长时间
使用explain sql--->并不会真正执行sql而是会给你列出一个执行计划#情况二:
有20条数据,但将来可能有2000000条数据
使用explain sql
6.9慢日志管理
#慢日志
执行时间 > 10未命中索引
日志文件路径#配置:
内存
show variables like'%query%';
show variables like'%queries%';
setglobal 变量名 =值
配置文件
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-log1. 启用#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=1slow-query-log-file=slow.log
long_query_time=3单位为秒
查看慢查询日志
测试:BENCHMARK(count,expr)
SELECT BENCHMARK(50000000,2*3);
View Code
慢查询优化的基本步骤
0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)3.order by limit 形式的sql语句让排序的表优先查4.了解业务方使用场景5.加索引时参照建索引的几大原则6.观察结果,不符合预期继续从0分析
View Code
6.10 sql注入
#用户名和密码到数据库里查询数据#如果能查到数据 说明用户名和密码正确#如果查不到,说明用户名和密码不对#username = input('user >>>')#password = input('passwd >>>')#sql = "select * from userinfo where name = '%s' and password = '%s'"%(username,password)#print(sql)
表结构及数据的插入
#创建表
create table userinfo(
id int primary key auto_increment,
name char(12) unique notnull,
password char(18) notnull
);#插入数据
insert into userinfo(name,password) values('eric','eric1234');
数据的查询
select * from userinfo where name = 'eric' and password = 'eric1234';
问题的出现: -- 注释掉--之后的sql语句
#密码随便出结果
select * from userinfo where name = 'eric' ;-- and password = '792164987034';#用户名输错也可输数据
select * from userinfo where name = 219879 or 1=1 ;-- and password = 792164987034;
select* from userinfo where name = '219879' or 1=1 ;-- and password = '792164987034';
解决sql注入
importpymysql
conn= pymysql.connect(host = '127.0.0.1',user = 'root',
password= '123',database='userinfo')
cur=conn.cursor()
username= input('user >>>')
password= input('passwd >>>')
sql= "select * from userinfo where name = %s and password = %s"cur.execute(sql,(username,password))print(cur.fetchone())
cur.close()
conn.close()
6.11数据备份和事务
6.11.1数据备份
退出mysql后执行
#语法:#mysqldump -h 服务器 -u用户名 -p密码 数据库名 > 备份文件.sql
#示例:#单库备份
mysqldump -uroot -p123 db1 > db1.sql #备份所有表
mysqldump -uroot -p123 db1 table1 table2 > db1-table1-table2.sql #备份指定表
#多库备份
mysqldump -uroot -p123 --databases db1 db2 mysql db3 >db1_db2_mysql_db3.sql
#备份所有库
mysqldump -uroot -p123 --all-databases > all.sql
重命名
6.11.2数据恢复
#方法一:
[root@egon backup]#mysql -uroot -p123 < /backup/all.sql
#方法二:
mysql>use db1;
mysql> SET SQL_LOG_BIN=0; #关闭二进制日志,只对当前session生效
mysql> source /root/db1.sql