一)为什么要有区,段,页?
1)页是内存和磁盘之间交互的基本单位内存中的值修改之后刷到磁盘的时候还是以页为单位的索引结构给程序员提供了高效的索引实现方式,不过索引信息以及数据记录都是记录在文件上面的,确切来说是存储在页结构中,另一方面,索引是在存储引擎中实现的,MYSQL服务器上面的存储引擎负责对于表中的数据进行读取和写入操作,不同的存储引擎中存放的格式是不同的,有的存储引擎例如Memory甚至都不用磁盘存储数据
2)InnoDB见数据划分成若干个页,页的大小默认是16KB,MYSQL以页作为磁盘和内存交互的基本单位,也就是说MYSQL一次至少将16KB的数据从磁盘加载到内存里面,一次也是至少把内存中的16KB数据刷新到磁盘上,也就是说从数据库中无论是读一行还是读取多行,都是将这些行所在的页进行加载,也就是说数据库进行存储空间的基本单位是页,数据库IO操作的最小单位是页,一个页中可以有多条行记录
3)页和页之间并不会在物理结构上直接相连,而是通过链表进行相连
1)什么要有区?
1.1)select * from user where userID>1 and userID<5,这些满足要求的数据可能分布在不同的页中,我们就需要找到所有数据所在的页,想要将页加载到内存中,实际上页和页之间在物理磁盘中离的是非常远的,MYSQL需要依次找到三个页的物理磁盘位置并把它们加载到磁盘中,因为磁盘中的磁头在去查找页的时候需要做寻道,盘片的旋转都需要时间,这种状况就称之为随机IO,随机IO中磁盘的读写速度和内存的操作速度相差了好几个数量级,在磁盘中查找页,如果页分散在磁盘上面的不同区域,找页就非常麻烦
1.2)顺序IO:所以说尽量让链表中相邻的页的物理位置也相邻,所以进行磁盘范围查询的时候方便,使用顺序IO,尽量让三个页在物理磁盘上是挨着的,是连续存放的,这样进行寻找的时候就能减少盘片旋转和磁道扫描的过程,这样加载速度就会快很多,分一个区就是为了保证一波页是在物理磁盘时尽量连续的,这样子就减少了随机IO;
1.3)但是区和区不一定是连续的,所以很有可能我们查询的数据都在一个区里面,多个页在一个区内,比之前多个页随机分布在磁盘上面的各个位置要靠谱得多
2)为什么要有段?
1)在区里面存放页,区里面的页是连续存储,现在还是进行查询
select * from user where userID>1 and userID<5,现在已经定位到userID大于等于1小于等于5这个范围了,现在感兴趣的只是B+树的叶子节点中所在的页
2)但是非叶子节点也是页,但是现在的区中极有可能存放叶子节点中的页,也有可能存放非叶子节点中的这些目录页,区中既有目录页也有数据页,但是感兴趣的只有数据页,因为都放,可能就会导致数据页放的少了,此时加载数据的时候,发现加载一个页数据项不够,又要跳转到下一个区,这个时候就不纯粹了,所以说能不能做到这样,特定的区只能存放叶子结点的那些页,有一些区专门存放非叶子节点的那些页,这样在进行定位数据的时候直接定位到叶子节点所在的区即可
3)一个段里面可能有多个区,非叶子节点多分配几个区,这些区都在一个段里面
存储引擎:指用来存储,处理和保护数据的核心服务,也就是数据库的底层软件组织
二)索引失效的场景:
最左匹配原则:指的是索引以最左边为起点的任何连续的索引都是可以匹配上的,但是当遇到范围查询(>,<,like,between)就会停止匹配,都是从联合索引的最左边开始进行匹配的
1)最左前缀法则和and的字段前后顺序没关系
2)通过key_len来看看哪个字段用到了索引
3)MYSQL可以为多个字段创建索引,一个索引可以包含16个字段,对于多列索引来说,过滤条件使用索引必须按照索引创建的顺序,依次满足,一旦跳过了某个字段,那么索引后面的字段都是无法使用的,如果查询条件不包含第一个字段,那么后面的索引都是不会使用的
这里是不会走索引的,因为只有第一种情况下才是最确定的情况,b c情况不确定,所以不走索引
主键插入顺序:
对于InnoDB存储引擎来说,在我们没有显示的创建索引的时候,表中的数据实质上都是存在于聚簇索引的叶子节点上面的,而数据是存放在数据页中的,数据页和记录又是按照主键从小到大的顺序来排列的,如果插入的记录的主键值是依次增大的话,那么每当插满一个记录页之后就在开辟一个记录页继续插,如果说插入的主键值忽大忽小就比较麻烦了,假设此时某一个记录页的数值已经存满了,主键值在1-100之间
可是这个数据也此时已经满了,应该怎么办呢,此时需要将页分裂成两个页面,把本页中的一些记录移动到新创建的页中,然后再找到合适的位置进行插入,页面分裂和记录移动代表着是性能损耗,所以尽量让插入的记录依次递增,这样就不会发生性能损耗了
一)不满足最左匹配法则:
二)索引列使用了列运算:
select * from user where id+1=4;
三)索引列使用到了函数:
select * from student where left(student.name,3)="abc",先针对于字段做用函数再依次进行比较,千奇白怪的函数多了去了,必须要进行全表扫描
四)模糊查询以%开头:
五)隐式类型转换:
如果索引列存在类型转换,那么也不会走索引,比如说某一列是字符串类型,但是查询的时候使用int类型的值就会导致索引失效
select * from user where username=123
六)范围条件右边的列索引失效,出现><<=>=between,在应用开发中例如说金额查询,日期查询往往都是范围性查询,应该将查询条件写在where的最后面,在创建的联合索引中应该把范围涉及到的字段写到最后(注意是写在索引的最后面,在等值查询的索引列写在前面,范围性索引列卸载联合索引的最后面)
下面这种情况范围查询后面的字段索引失效:
但是如果修改成这样子:create index idx_age_name_classid on student(age,name,classID)
这时候就使用到了全部的索引,主要取决于索引中先写谁后写谁
七)<>或者是!=导致索引失效,也是一种不确定的情况
八)is null可以使用索引,is not null不可以使用索引(遍历索引项一个一个的全都看一遍)
最好是在进行设计数据表的时候将字段设置成not null约束,这样在进行查询的时候就不会使用is not null了,如果说非要使用null值,可以将int类型的字段默认值设为0,字符类型的默认值设置成空字符串,同理,在查询中使用到not like也是无法使用到索引的,也会导致全表扫描;
九)or前后存在非索引列,索引失效,也就是前后的字段必须都有索引
select * from user where userID=1 or age=10,假设这里面的userID有索引,age没有索引,那么此时查询一定会走全表扫描,因为就算userID走的是索引,但是仍然要全表扫描找age=10的字段,此时还不如走一下全表扫描来的快
如果使用了
or
关键字,那么它前面和后面的字段都要加索引,不然所有的索引都会失效,这是一个大坑。10)数据库和表的字符集统一使用utf8b4,统一字符集可以避免由于字符集转换而产生的乱码,不同的字符集进行比较从而进行转换的时候涉及到转换函数,导致索引失效
12)索引列上进行对比:可以导致索引失效
13)in通常是走索引的,当in后面的数据在数据表中超过30%的匹配时,会走全表扫描,即不走索引,因此in走不走索引和后面的数据有关系,exists也走索引
in有可能走索引也有可能不走索引
14)如果是小范围的查询,还是走索引的,type属于range,在随着数据量的增大时会自动进行全表的扫描(并且与要查询的结果是否包含在索引树中决定走index还是all);
not in则不走索引;
对于order by来说:
1)没有加where或者是limit不走索引
2)对多个索引进行order by操作
3)不满足最左匹配原则
4)不同的排序
聊聊索引失效的10种场景,太坑了 - 知乎 (zhihu.com)
三)索引覆盖:
覆盖索引是指在某一次查询里面,某个索引的值已经包含了所有的查询需求,此时不需要再次进行回表查询了,假设下面的查询针对于age建立了索引
假设现在针对于c2列进行查询,select * from user where c2>=1andc2<=5,如果需要进行回表查询的话,那么这些数据可能在一个数据页中也有可能不在同一个数据页中,当然这些页也是连续的,64个页在同一个区里面,很有可能索引中挨着的字段实际上存储在不同的区中,回表的时候使用随机IO的,所以覆盖索引就将将随机IO转化成顺序IO
四)索引下推:
索引下推是MYSQL5.6引入的新特性,是一种存储引擎使用索引过滤数据的一种优化方式
索引下推是指在索引在进行遍历的过程中,对索引中包含的字段先进行判断,先直接过滤掉不满足要求的记录,减少回表查询的次数就叫做索引下推,Using index condition,只有触发联合索引才能出发索引下推
索引中有这个字段,但是这个字段又索引失效了,没有办法使用到到这个索引,就可以使用索引条件下推来对索引的记录进行过滤
1)select * from user where key1>10 and key1 like "%张三";
正常情况下,我们是来考虑key1字段大于10会使用到索引,like查询以%开头是不会使用到索引的,所以程序会先根据key1>10在非聚簇索引找到主键ID,进行回表查询的时候再去找key1字段like"%张三"这样的一个查询过程,但是真实的查询优化器,现根据key1>10找到了100条记录,但是此时不会发生回表查询,二十次是根据key1 like "张三"的条件进行索引下推,然后根据这个条件进行判断,然后减少回表的次数
同理 select * from user where a>10 and a like "张%",此时是不是a like查询条件不会使用到a索引,但是实际上使用到了使用到了索引下推,但是本质上还是用到了a索引
2)index(a,b)创建联合索引,explain select * from user where a="01" and b like "%张",此时虽然 b字段不会使用到索引,但是可以使用到索引下推来进行过滤,减少回表查询的次数,从而减少随机IO的次数
3)ICP不用于覆盖索引和覆盖索引
4)select * from user where name like "张%三" and age=10,这个语句在用到索引搜索树的时候,只是找到第一条满足条件等于张的记录
1)在MYSQL5.6之前,因为使用的是联合索引,它会先找到姓张的字段所有的记录的主键ID,而不是根据先找到name=张某某的所有的人的记录在接下来根据这些张某某的字段再来对age做进一步的筛选,而是再拿着第一条是张某某的所有四条记录的主键ID进行回表查询找到完整记录,根据完整的记录然后再来进行判断age再来判断ismale;
2)只有到了MYSQL5.6之后才会进行联合查询的时候才会使用到多个查询条件,根据姓张的找到了好几条,然后再根据age筛选出年龄不符合条件的,只剩下两条在进行回表查询,根据age过滤减少回表查询的次数就叫做回表查询
3)索引下推主要体现的是索引中有这个字段,但是这个字段又失效了,没有办法使用到索引的这个部分,那么这个时候使用索引条件的下推来对会表的失效的索引列再做一个筛选判断,所以说谈到索引下推就是首先使用联合索引,第二下推的条件列索引失效,但是程序还是根据这个索引列进行了筛选了数据从而减少了回表查询的次数
4)补充:varchar(50)对应的keylen是多少?
utf8一个字符占用三个字节,有一些特殊的复杂的汉字是不能够表示的
存储引擎使用的是utfmb4,这个字符集可以支持所有的复杂的汉字,一个char是使用4个字节来存储的,隐含的条件是varchar类型是不定长度最大长度是50的一个字符串类型,具体字符串所占用的空间大小是不定的,是根据值的大小来做申请空间的,还要额外使用2个字节来存储实际字符串不定长所存储的长度;
5)对于age来说,keylen是多少呢?5,因为额外要使用一个字节来判断是否为null,相当于是否为null,如果是not null,那么keylen就是4;
1)最左前缀法则比如说(a b c)建立联合索引,select * from user where a=10 and b>8 and c=3,它这个只有a和b走索引,b是根据索引列进行范围查询,但是我感觉他为啥c也不走索引呀,这样MYSQL设计有点不好吧,他这个联合索引是先按照a排序,a相同,再按照b排序,b相同再按照c排序,就我上面的那个SQL语句,完全先找a=10的,再从a=10的找b大于8的,然后在b大于8的找c=3的这样子也挺快呀?但是实际上c不走索引感觉有点不好,MYSQL为啥要这样子设计呀
2)况且来说遇到"张%"后面的字段为什么也不是用到索引呀index(a,b,c)
select * from user where a=10 and b like"张%" and c=10,为什么c字段不使用到索引呢?
为什么遇到>
此时索引底层是怎么走的呢?
1)针对c字段进行索引下推
2)根据a=10和b进行查询到主键ID,进行回表查询找到c=10的记录过滤
2)索引下推有一个问题:
那么如果针对于(name,age,address)变成select * from user where name="zhangsan" and age>10 and address like "%西安xx" and ismale=1,也会针对于address进行下推吧,虽然age以后字段不在走索引,但是优化器针对于address进行了下推,是不是经过优化器优化导致最终的结果是address也使用到了索引,因为使用address的条件进行了下推,所以也叫作address使用到了索引)(如果没有索引下推,根据索引失效原则,是不是address没有用索引
所以说MYSQL针对于索引下推的字段到底用没用到索引呢?那说下推的字段叫做使用到了索引对吗?但是MYSQL的确是针对于这个条件进行了过滤呀?
如果索引下推是针对于联合索引中某一个索引列出现了范围查询导致(比如说like between ><)之后一个字段不再使用联合索引,那么索引下推就是根据后面的这个索引列进行判断
3)in exists not in和not exists到底走不走索引呀
4)范围条件右边的列索引失效,是不是根据同一个索引或者是联合索引来说的
select * from user where a>10 and b=10,此时a和b建立了不同的索引,此时a和b都是用索引吧或者说a使用索引或者是b使用索引,但是如果是a,b是联合索引,上面的这一条SQL语句b就一定不是用到索引了吧
5)select * from user where a like "张%%%" and c>10,此时index(a,b,c)此时c索引下推吗?select * from user where b like "张%%" and c>10 此时是不是索引不下推了
五)事务的四大特性:
1)原子性:指的是事务本身是一个不可分割的工作单位,要么全部执行成功,要么全部执行失败,要么转账成功,要么转账失败,不存在中间状态,如果无法保证原子性,那么就会出现数据不一致的场景A账户减去100元,B账户增加100元增加失败
2)一致性:指的是事务在执行前后,MYSQL中的数据始终处于一种合法性的状态,也就是说在进行转账前后,A,B两个人的总钱数是不变的
3)持久性:指的是事务一旦提交,提交的数据对于数据库的修改就是永久性的,接下来的操作和数据库的故障不应该对其有任何的影响
持久性是根据重做日志来实现的,但事务针对于数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,提交后在针对数据库中的数据进行修改,即使数据库的系统崩溃,数据库重启之后也是可以找到重做日志中的内容来进行恢复
4)隔离性:指的是一个事务的执行不能被其他事务所干扰,即一个事务内部的操作以及使用到的数据对于并发的其他事务来说是隔离的,并发执行的相互事务之间不能干扰
开启一个事务:start transaction或者是begin表示开启一个事物,后面可以加上一个read only表示当前事务是一个只读事务,也就是该事务只能读取数据而不能修改数据
1)savepoint 保存点名称,在事务中创建保存点,方便后续针对于保存点进行回滚,一个事务中可以存在多个保存点
2)删除某一个保存点:release +保存点名称
3)是事物回滚到某一个保存点:rollback to savepoint
set autocommit=true; show variables like "autocommit" 在autocommit=true的情况下,每一个DML语句都是一个事务,只是针对DML语句
脏读:读到了其他事务没有进行提交落盘的数据
不可重复读:读到了其他事务提交的数据,两次查询数据的过程中数据被其他事务修改了,侧重于修改
幻读:两次查询数据的过程中数据被其他事务新增了数据或者是删除的数据,侧重于新增
串行化:保证数据的完全隔离,
查看MYSQL的隔离级别:
select @@global.transaction_isolation,@@transaction_isolation;
六)MYSQL是如何保证数据库四大特性的?事务底层是怎么实现的?
一致性:两个数据源操作的时候前后数据始终处于合法的情况
隔离性:指的是并发事务之间相互执行时相互之间对于结果的可见性
1)保证原子性:undolog回滚日志:
进行事务一个MYSQL非查询操作之前,会现在回滚日志会先记录当前执行的这个操作,当进行转账操作的时候,张三要捡钱,一旦发现转账失败,那么直接进入到undolog日志查询这个操作,然后在进行相反操作即可,直接给张三加钱即可;
2)保证持久性:redolog重做日志,将修改的数据放到redolog里面,可以减少IO操作,因为每一次写操作都将数据写入到磁盘中效率是非常低,还会增加随机IO,事务提交以后再把reddolog日志刷新到磁盘里面,还可以在系统恢复和断电以后再进行数据恢复;
3)隔离性:MVCC(多版本并发控制,不能完全解决隔离性)和锁来实现的
4)一致性:是通过各种约束,主键外键唯一性约束+其他三大特性来保证的
既然这俩日志也是持久化,但是为什么不把数据直接持久化到磁盘上面的数据里面呢?而是直接放到日志里面?
保证MYSQL效率,保证系统崩溃恢复的主要手段,减少IO操作
1)因为持久化redolog速度非常快,效率很高,因为重做日志不需要考虑任何东西,只是单纯的数据的保存,存储的结构非常简单
2)但是如果一直更新磁盘上面的表数据,因为MYSQL要把这些数据分门别类地存放到磁盘上改存的地方,比如说A表已经存放了2亿条数据了,如果你此时向这个表中新增数据是很慢的,如果此时这个表里面有索引,此时不但要填充数据,还需要填充索引的值,所以说直接更新数据效率是很低的,但是redolog直接只存放数据信息,下一次直接进行刷盘即可,保证MYSQL效率,保证IO次数尽量少,还可以事务未提交防止断电重启数据丢失
七)MYSQL中的日志详解:
事物的四大特性本质上是依靠日志来实现的,事物的隔离性是依靠锁机制来实现的,但是事务的原子性,持久性和一致性是依靠事务中的redolog和undolog日志来实现的
1)redolog被称之为是重做日志,提供再写入操作,用于恢复提交事务修改的页操作,用于保证事务的持久性,他的底层记录的是物理级别上面的页的操作,比如说页号XXX,偏移量XXX位置写入了XXX数据,主要是为了保证数据的可靠性
2)undolog被称之为是回滚日志,回滚记录行到某一个特定版本,用于保证事务的原子性一致性,他也是存储引擎层生成的日志,记录的是逻辑操作日志,比如说在针对某一行进行了insert操作,那么undolog就纪录一条与之相反的delete操作,主要用于事物的回滚,本质上记录的是每一个修改操作的逆操作和一致性非锁定读,undolog回滚记录到某一种特定的版本MVCC,就是多版本并发控制;
一)redolog日志详解:
1)innodb存储引擎是以页为单位进行管理存储空间的,在真正访问页面之前,需要把磁盘上面的页缓存到内存中的buffer pool中才可以进行访问,所有的变更必须先更新缓冲池中的数据,然后将缓冲池中的脏页以一定的频率刷入磁盘中,脏页就是内存中改了但是磁盘中没改
2)为什么说是以一定的频率刷新到磁盘中呢?
因为从内存写入磁盘本身就慢,经常刷盘,效率很低,刷盘中的页本身可能是不连续的,随机IO,性能不太好
3)假设事务里面有很多DML操作,这些DML操作都是操作的是内存中的数据,这个时候事务提交了,内存中的数据的修改都完成了,此时磁盘还没有被写入刷盘,逻辑上磁盘上的数据应该被修改了,但是此时commit操作MYSQL宕机了断电了,内存中的数据还没刷盘,如果这个时候没有redlog日志,再次重启内存中的数据没了,磁盘上的数据还是从前的,相当于事务从来没有执行过,磁盘上的数据没有修改,此时事务就不保证持久性;
4)一方面缓冲池的存在可以保证数据的最终落盘,但是由于缓冲池并不是由每一次变更的时候就进行触发的,而是说是由master线程隔一段时间来进行处理的,所以最坏的情况下就是事务提交了,刚写完缓冲池,数据库就宕机了,那么这段数据就是丢失的,无法恢复,另一方面是说事务包含持久性的特质,就是说对于一个已经提交的事务,即使在事务提交以后系统发生了崩溃,这个事务对于数据库的修改也是不能丢失的,那么如何来保证持久性呢?
一个简单粗暴的方法就是在事务提交完成之前将该事务修改的所有页面都刷新到磁盘上
就是一旦内存中的数据被修改了,那么马上就去更新磁盘上的数据,实时的修改,可不可以呢?别等到事务提交的时候才去更新磁盘上的数据
4.1)修改量和刷新磁盘工作量严重不成比例:
4.2)随机IO太多:修改的页可能不连续
redolog的好处和特点:
1)redolog日志降低了刷盘频率,减少磁盘IO
2)redolog日志占用的空间非常小,只是存储表空间ID,页号偏移量以及要更新的值,所占用的空间非常少,刷盘就快