MySQL高级
- 介绍一下MySQL索引原理和数据结构
- B+树和B树的区别
- MySQL聚簇索引和非聚簇索引的区别
- 使用MySQL索引的原则
- 不同的存储引擎是如何进行存储的
- MySQL的组合索引结构是怎么样的
- MySQL索引如何进行优化
谈一下你对MySQL索引的理解?
- 数据结构
- B+树(为什么?)
- IO
- MySQL
如果让你来设计一个索引系统,你如何设计?
- MySQL的索引中存储什么样的数据?(key-value(文件编号和offset))
如果数据量大索引占用的存储空间会不会大
key 文件编号 当前文件的offset(偏移量) 索引的文件存在内存还是磁盘?
索引的数据文件需要持久化存储,存储在磁盘当中,需要使用的时候,把数据从磁盘中读取到内存中,加快数据的访问;
采用分而治之的方法:分块读取
局部性原理:数据和程序都有聚集成群的倾向,之前被查询到的数据很快会被再次查询;冷热数据(热数据:经常被查询的数据;冷数据:很少被查询的数据)磁盘预读:在数据交换的时候会使用操作系统的基本单位是'页'一般占用空间是4K与操作系统相关,也可以是8K,每次在进行数据获取的时候,获取页的整数倍(读取数据时候,使用页的整数倍来读取数据,MySQL中的inondb的存储引擎读取数据的时候会读取16Kb)
使用什么样的数据结构可以存储16K、16K这样的数据呢?
哈希表
如果要进行范围查找的话怎么办?(不能进行范围查找)
挨个遍历每一个元素,匹配对应的范围,如果全部是等值查找的话可以使用hash表
碰撞-冲突(会出现大量哈希冲突,解决哈希冲突需要设计更好的hash算法)
数据散列不均匀,而且浪费存储空间,需要设计足够好的hash算法
是否需要大量的内存空间(hash不能读取一部分,全部读取会消耗很多内存空间)
如果存储的元素比价多,会占用大量的内存空间,而且不能分块读取
综上hash存储数据可能不是那么合适;
不同的存储引擎是如何进行存储的.
但是在MySQL中给出的**存储引擎(inondb、myisam、memory):数据文件在存储介质中的不同组织形式。**例如在inond文件中,frm存储表结构,ibd存储索引和数据;在myisam中frm存储表结构,MYD(my data)存储数据,MYI(my index)存储索引。
MySQL5.7官方文档给出的memory是使用hash数据结构的,直接给够MySQL内存
- 树
- B树
- B+树
为什么要建索引?
提高数据的查询效率,核心问题是IO
- IO量
- 减少IO量
- IO次数
- 减少IO次数
所以在进行数据的读取和设计的时候就需要考虑这两个问题。例如,不要写
select *
只需要几个字段不需要全部查询,这样会增大文件的IO,降低查询速率
B树/B+树
B树结构:
一次数据检索的时候如果走到节点就占用了这部分空间,那么一次检索的时候就占用了16+16+16的空间
假设一行数据占1KB那么三层就可以存储16*16*16=4096条数据,这样存储的数据还是比较少的。因为其中data占用了大量的存储空间,会导致这个树变慢变深。这时候就要想到B树的变种B+树
B树
B+树
B+树:匹配某一个范围去检索对应的数据行记录
B+树放弃了每层存储的data域,而是改用每层都去存储指针和key值。这样的组合假设一对指针和key键占用内存10字节,在叶子节点存放所有的data数据,那么三层就可以存储16*16*16*1024*1024/10/10=42949672.96条数据,相比于4096条数据可以说是非常大的提升了
B+树保证了能检索的数据量是足够多的
两种查找方式:
- 从根节点开始向下查找
- 从叶子节点开始从左向右按顺序查找
在选择key的数据类型的时候是int类型好还是varchar类型好?
int类型好,如果还是上述条件,一对指针和key键占用了100KB内存那么三层可以存储的数据量将会缩小到429496.7296。也就是说在选择key键的数据类型的时候我们要尽可能的占用更少的空间,才能存储更多的数据
MySQL的B+树一般是三层还是四层还是几层?
一般在使用MySQL的时候如果是千万级别的数据量3-4层足矣。为什么是三到四层,因为和你存储的key有关系,你的key占用的空间越小,存储的数据量越多,树越矮;key占用的空间越大,存储的数据量越少,树越高。
在使用MySQL主键索引的时候,主键要不要自增?
关键的一个点就在于:索引的维护 ,如果不去设置递增的话,当
磁盘块5
满了而且要插入数据14
的时候,就需要进行叶的分裂,不接叶子节点需要修改,叶子节点的父节点也需要修改。而如果设置了主键自增的话,不会影响前面的结构。(但是也不是都用自增,例如分布式集群就不能自增了,所以在满足当前需求的情况下能自增尽量自增)
MySQL中删除数据会真的将数据删除然后将内存进行回收嘛?
不会,只是做标记,下一次会将数据直接覆盖进去
MySQL聚簇索引和非聚簇索引
判断数据索引是否是放在一起的如果放在一起就是聚簇索引,不放在一起就是非聚簇索引
数据一定和索引聚集存放
在inondb中数据在进行插入的时候,必须和某一个索引进行绑定,这个值默认是主键,如果没有主键那么选择唯一键,如果没有唯一键那么选择六字节rowid
inondb中相同文件名的有两个文件(.frm .ibd)
问题:
-
一个表中可以有多少个索引?
N个
-
索引如果是和数据放在一起的那么数据会存储几份?
数据会只存储一份
如果存储这样第一个表结构id,name,age,gender,address。id是主键,name是普通索引,在整个标中就会有两个B+树,主键是和数据存放再一起的,name所在的B+树叶子节点存放的是id值
如上图如果使用这样一条语句进行查询select * from table where name=?
实际上是查找了两颗B+树。先在name的B+树种查找到id,然后根据id去查找对应的节点数据。这里面id就是一个聚簇索引了,name就是非聚簇索引
- 所以在innodb中一定有聚簇索引,但是其他索引都是非聚簇索引,所以inondb既有聚簇索引又有非聚簇索引
- 对于myisam中只有非聚簇索引,因为本身数据和索引就分开放了(.frm .MYD .MYI)
使用MySQL的索引的原则
面试中关于索引的技术名词
- 回表
- 索引覆盖
- 最左匹配
- 索引下推
现在有一个表,id,name,age,gender,id是主键,name是普通索引。这样一个表结构
回表
mysql
select * from table where name = zhangsan;
当有这样一个语句将会遍历两颗B+树,从非聚簇索引跳转到聚簇索引中查找数据的过程称之为回表。
- 回表的效率是不高的,因为遍历了两颗B+树,因此要尽量避免回表操作
索引覆盖
mysql
select id, name from table where name = zhangsan;
索引覆盖和回表是一个相反的过程,当非聚簇索引的叶子节点包含了查询需要的所有字段时,不需要回表,这个过程称之为索引覆盖。
- 推荐使用索引覆盖
*联合主键
最左匹配
表id,name,age,gender,id是主键,name,age是普通索引或者叫组合索引
mysql
select * from table where name = ? and age = ?;
select * from table where name = ?;
select * from table where age = ? ;
select * from table where age = ? and name = ?;
在有组合索引的时候会有最左匹配 ,其中只有1,2,4使用了组合索引(只查age的时候就是全表扫描),最左匹配按照组合索引从左到右去匹配,其中后面的索引也可以不去匹配,但是不能直接去匹配后面的索引,第四条语句会使用MySQL中的一个优化器,他会帮助我们挑中语句的顺序达到想要的一个结果。
mysql
create table if not exists abc(id int primary key, a int, b int, c int);
alter table abc add index idx_1(a, b, c);
mysql
explain select * from abc where a=1 and b=1 and c=1;
查看执行计划,看到key是idx_1所以使用了组合索引
mysql
explain select * from abc where b=1 and c=1;
explain select * from abc where c=1;
这两条语句也都使用了组合索引
mysql
create table if not exists abcd(id int primary key, a int, b int, c int, d int);
alter table abcd add index idx_2(a, b, c);
上图为非全字段组合索引,可以看出,当组合索引是全字段的时候单个拿出也可以使用组合索引最左匹配,但是如果不是全字段的时候缺少第一个参数是不可以使用组合索引的。当表中全部列都是索引列的时候,无论怎么查询都会用到索引。
- 那么给abcd表中的d添加单独索引,再去执行这个语句会走索引吗?
mysql
alter table abcd add index idx_3(d);
explain select * from abc where b=1 and c=1;
不会;无法查询到全量的语句,不用走索引。
索引的一些问题
mysql
explain select * from abcd where a = 1 and b = 1 and c = 1;
explain select * from abcd where a = 1;
-
上述两个语句,使用的索引相同吗?
相同
-
上述两个语句,索引的长度是多少(key_len)?
15和5。一个int类型占用4字节,可以为空额外占用1字节,所以一个索引占5字节
mysql
create table if not exists abcd2(id int primary key, a int not null, b int not null, c int not null, d int not null);
alter table abcd2 add index idx_4(a, b, c);
explain select * from abcd2 where a = 1 and b = 1 and c = 1;
explain select * from abcd2 where a = 1;
索引下推
mysql
select * from table where name = ? and age = ?;
- 没有索引下推之前,执行的过程是:先根据name从存储引擎中拉取数据,然后根据age在server中过滤
- 有了索引下推之后,执行的过程是:根据name,age整体从存储引擎中做数据检索,返回对应的记录,不在server层做任何操作
把我们sql语句做的事从server层下推到存储引擎层,这叫做索引下推
MySQL事务
- 事务的四个特点是什么?他们的实现原理是什么?
- MySQL的redolog、undolog、binlog分别有什么用?
- 什么是二阶段提交,如何保证宕机时数据的一致性?
- MVCC如何实现多版本并发控制?如何解决读写冲突?
- MySQL中的幻读是什么?如何解决幻读问题?
MySQL事务的特点
事务的特点:(4个)ACID(实现方式)
原子性
要么全部成功,要么全部失败(涉及事务回滚)
- 原子性通过undolog来实现的
MySQL日志系统
binlog是在server层次中的,而unodlog和redolog是在innodb存储引擎中的(myisam是不支持事务的,innodb是支持事务的,这两个区别重点聊事务)
binlog(二进制日志文件)(默认不开启):可以进行数据同步和数据恢复(推荐开启)
undolog(回滚日志):
redolog(前滚日志):主要是为了数据持久化和数据恢复时一致性存在;当有数据来了之后先将数据写入redolog之中去,即使数据没有更新也没有关系,当有时间的时候,将redolog中的数据进行一个重写,而不是花费大量的时间去查找数据位置然后进行更新。redolog写数据的同时也会进行一个数据的同步,这个redolog只是为了在出现问题的时候,可以回去进行查找;redolog是一个固定大小的空间 ,在这个空间被填满的时候会进行覆写,最前面的数据将会被覆盖。如果redolog中的数据没有写成功,而且实际数据文件也没有写成功,就会产生数据丢失的问题。数据库只是提供这样的一种机制,并不会完完全全保证数据不会丢失。
在记录数据的时候redolog和binlog都会对数据进行记录,这时就有一个问题:先写redolog还是先写binlog?
- 这个问题是一个坑!不管先写redolog还是先写binlog都有问题。这里涉及到另外一个知识点:两阶段提交。
两阶段提交
binlog和redolog要保证两阶段提交这个事情,两个日志数据文件中要不然都写,要不然都不写,如果一个写了一个没写需要判断数据是否生效(数据是否最终时一致的)(很重要) ,在进行操作的时候,两个日志会同时写,恢复数据的时候会根据两个日志去恢复。这里就涉及到,先写谁后写谁?
先写redolog后写binlog:
当写完redolog没写完binlog的时候会有什么情况发生?:操作A数据库,B数据库是通过binlog来进行数据同步的,此时如果突然断电,A数据库已经做完了这条操作,B数据库根据binlog同步,但是binlog中并没有这条操作,这时候AB数据库数据就不一致了,这时候主从同步就失败了
先写binlog后写redolog:
同理,A机器会缺少一条数据,B机器里会多一条数据。此时数据也不一致
两阶段提交 :先写redolog只不过此时,redolog会处于prepare状态。再去写binlog,等到事务提交了再将redolog置为commit状态。在这一部分执行的过程中,依然可能出现断电的状态,这时候的机制是这样的:在出现断电的时候回去检查redolog的状态,如果redolog处于prepare状态证明redolog已经写完了,断电发生在redolog之后,这时就回去对比redolog和binlog的是否记录了相同的操作,如果不相同证明断电发生在binlog完成之前,这时候已经prepare的redolog中的数据将被舍弃,如果两部分记录的操作是相同的,那么证明binlog已经完成只不过还没有进行提交这时候只需要将redolog'的prepare状态改为commit状态,进行提交即可。如果redolog不是prepare状态就证明redolog都没有记录完成这时候更不可能记录binlog了。
*binlog和redolog这样的提交状态可以替换吗?:binlog没有prepare这样的状态.
两阶段提交的执行流程:
- 执行器从引擎中找到数据,如果在内存中直接返回,如果不再内存中查询后返回
- 执行器拿到数据之后先修改数据,然后调用引擎接口重新写入数据
- 引擎将数据更新到内存,同时写数据到redo中,此时处于prepare阶段,并通过执行器完成,随时可以操作
- 执行器生成这个操作的binlog
- 执行器调用引擎的事务提交接口,引擎把刚刚写完的redo状态改为commit状态,更新完成
errorlog(错误日志):MySQL执行过程中,出现错误记录到日志之中去
slowlog(慢日志):在数据库中开启慢查询,根据日志判断那条sql语句执行比较慢,然后做出相应的调整
relaylog(中继日志):主从复制或主从同步的时候用到
一致性
一致性是我们根本的追求,一致性的实现方式是由其他三个特点来保证的
隔离性
并行运行的事务相互独立,互不干扰,一个事务正在运行,当前另一个事务不能影响当前正在运行的事务
- 隔离性通过锁机制实现(加锁之后的效率会变低)
- 通过MVCC(多版本并发控制)和锁实现
MVCC(多版本并发控制)
并发情况:
- 读读:不存在任何问题,也不需要并发控制
- 读写:有数据安全问题,例如脏读,幻读,不可重复读
- 写写:有数据安全问题,可能存在更新丢失问题
基本概念:当前读和快照读
当前读和快照读
当前读:在进行数据读取的时候,读取的时候都是最新版本的数据,而且在读取的时候还要保证其他并发事务不能修改我们当前的事务
触发当前读操作:
- select lock in share mode(共享锁)
- select for update(排他锁);update、delete、insert(加写锁)
快照读:读取的是历史版本的数据
触发快照读的操作:
- select
MVCC执行操作:三部分操作隐藏字段:
- DB_TRX_ID:创建这条记录或者最后一次修改记录的事务id:在操作事务的时候id是递增的
- DB_ROLL_PTR:回滚指针,指向上一个数据版本:没有历史版本指向null,有历史版本指向历史版本
- DB_ROW_ID:隐藏主键,如果没有显式主键的话,就会多一个隐藏主键:没有主键给一个默认的值,有主键写上默认的主键
这些隐藏字段会在创建的表之后,但是无论怎么查询都是查不到的
undolog:
回滚日志:记录的是数据的历史版本
undolog中会形成一个链表,链首是最新的旧纪录,链尾是最旧的旧记录,但是数据不会无限制的增大,后台有一个线程purge线程清除没用的数据
readview
事务进行快照读操作的时候产生的读视图(包含三个关键字段)
- trx_list:readview生成时刻当前系统活跃的事务id
- up_limit_id:活跃列表中最下的事务id值
- low_limit_id:系统尚未分配的下一个事务id的值
上面两次操作产生快照读的对应字段分析,红色部分可以读取到修改的值,绿色部分不能读取到修改的值为什么?猜测如下图:
结论:在第二次进行快照读的时候雀实使用了第一次生成的readview,而没有重新生成隔离级别:
- RC:能
- 在RC隔离级别中,每次进行快照读操作的时候都会重新生成新的readview,所以每次可以查询到最新的结果记录
- RR:不能
- 在RR隔离级别中,只有当前事务在第一次快照读的时候会生成readview,之后进行快照读都会沿用之前的readview
持久性
数据一旦提交就是永久性的不会因为宕机等导致数据丢失
- 通过redolog实现
IO
随机IO:寻址之后写入
顺序IO:直接在地址末端进行append追加
肯定是直接追加的效率高
MySQL中的幻读是什么?如何解决幻读问题?
幻读通过加锁的方式来解决的
开启两个事务,一个事务先做读取,另一个事务也做读取,查询结果相同,另一个事务更新其中一条数据并提交,然后一个事务进行查询,因为在RR隔离级别下,只会在第一次快照读的时候产生readview索引查询结果和第一次相同,但是这时候去对查询结果进行修改操作,会发现修改的记录比查询到的记录要多,这就是发生了幻读
幻读问题产生的本质原因是什么?
如果事务中进行操作的都是快照读,那么是不会产生问题的,但是当前读和快照读一起使用的时候才会产生幻读问题。
间隙锁
间隙锁(-∞,1)、记录锁(行锁)1、间隙锁(1, 3)、记录锁3、间隙锁(3,5)、记录锁5、间隙锁(5,7)、7、间隙锁(7,+∞)
临键锁:左开右闭的一个区间【就是间隙锁和行锁合在一起了】
(-∞,1],(1,3],(5,7],(7,+∞)
锁是给索引加的
主从一致
MySQL主从复制是一种实现数据备份、读写分离和负载均衡的方法。它通过将一个MySQL数据库实例(称为主库或者主节点)上的数据复制到其他MySQL数据库实例(称为从库或者从节点)上来实现。这样,当主库发生故障或需要进行维护时,从库可以接管主库的工作,保证数据的可靠性和高可用性。
MySQL主从复制的实现方式分为三个步骤:复制开始、复制过程和复制结束。具体过程如下:
- 复制开始:从库通过连接到主库的方式请求复制,主库接收到请求后记录从库的连接信息和已有数据的位置,然后开始发送二进制日志文件给从库。
- 复制过程:主库将修改数据的操作记录在二进制日志文件中,并按照一定的规则将二进制日志文件发送给从库,从库接收并执行这些日志文件,从而实现数据的复制。
- 复制结束:当从库赶上主库的数据更新时,从库将成为主库的一个完全相同的副本。此时,从库也可以接收来自其他从库的复制请求,实现级联复制。
在MySQL主从复制中,主库负责写入数据,从库负责读取数据。通过主从复制,可以实现读写分离和负载均衡,从而提高数据库的性能和可靠性。此外,主从复制也可以用来进行数据备份和数据恢复,保证数据的安全性和完整性。
记
早期的笔记,最近学数据库看到拿出来看看,笔记源自于黑马的课程,自己记录的一些东西,上传图片需要重新放,希望没有篡位