mysql整理
1.架构
sql语句: select * from t1 join t2 on t1.id = t2.id
主要组成:客户端,服务器,存储引擎
1.1客户端:(jdbc,navicat,sqlyog)
负责建立连接提交sql
1.2服务器
- 连接器:负责管理连接,验证权限
- 分析器:负责词法分析(了解抽象语法树AST:antlr,apache calcite)
- 优化器:根据RBO规则优化方式或者CBO成本优化规则进行优化,具体实现需要看源码
- 执行器:执行SQL跟存储引擎交互
1.3存储引擎:(myisam,innodb,memory)
不同的数据文件在磁盘的不同组织形式
.frm表结构相关元数据文件 .ibd 数据结构,索引 innodb .MYD data文件 .MYI index文件
*查询缓存因为需要经常更新里面的数据,所以被取消
2.索引
2.1索引的意义
加快数据访问,提高查询效率,索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容.
需要考虑的问题:数据存储在磁盘,从磁盘读取数据的效率低
产生以上问题的原因:需要IO
解决方法:1.减少IO量(需要读10m绝不读11m) 2.减少IO次数(能一次读取,绝不读两次)
2.2索引的数据格式
数据是放在一个十分大的文件里,需要定位到文件中的位置
创建索引时,会采用k-v格式存储数据,创建k值,根据k值获取v值
1.首先了解两个技术:
技术 | 含义 | 解释 | 对应类型 | 典型代表 | 采用的数据结构 |
---|---|---|---|---|---|
OLAP | 联机分析处理 | 对历史数据进行分析,产生决策影响,不要求短时间内返回结果 | 数据仓库 | hive | 索引文件 |
OLTP | 联机事务处理 | 支撑业务系统的需要,在短时间内返回对应的数据结果 | 数据库 | mysql | B+树 |
*索引文件:k:字段值 v:文件名称,偏移量,数据长度
*索引文件不适合数据库的原因:当数据文件变多时,索引文件也会变大
2.存储k-v格式数据的时候还可以使用什么数据结构:
哈希表->二叉树->红黑树->B树->B+树
哈希表:
1.使用哈希表的意义是为了让数据尽可能的散列,因此在使用哈希表的时候要选择合适的哈希算法,避免哈希碰撞和哈希冲突
2.哈希表的存储的数据是无序的,当需要进行范围查询的时候,只能挨个进行遍历,效率极低
mysql的memory存储引擎支持哈希索引,innodb存储引擎支持自适应hash
二叉树:
BST:二叉搜索树 AVL:平衡二叉树 红黑树:红黑节点,详见红黑树相关内容
二叉树在存满的情况下,至多只能存储2^n-1条结果,如果想存储更多的数据,只能将树的高度提高,变成四层或五层或者更多层,那么层数变多了会有什么影响呢?
io次数变多
磁盘预读:内存跟磁盘进行数据交互,有一个最基本的逻辑单位,称之为页或者叫datapage,页的大小是跟操作系统相关的,一般是4k或者8k,我们在进行数据的读取的时候一般操作的都是页的整数倍
数据量十分大的时候没有办法一次性读取 解决方法:分块读取,分而治之,磁盘预读就提供了天然的分页读取,这是操作系统给的,但是二叉树每个节点只能放一个值,相当于每个节点就是一个页,如果存不满一个页的大小,那么会出现浪费的情况,同时需要存的东西变多时,因为不能再往以前的节点添加数据(一个节点只能存放一个数据),所以只能加节点数,也就意味着要加层数,因此io量会增多
尽可能多的存储数据还要减少io的次数,减少数的高度
选择多叉树,同时保留二叉树的平衡,有序,B树就诞生了
B树:
Degree度,每个节点只能存放n-1个数据
需要存放的信息:key值id value值行数据,下一个节点的地址
b树的搜索保留了二叉树的性质,最左边的小,最右边的大
show variables like ‘%innodb_page_size%’;
mysql默认每次读取16kb的数据
假设每个data占1kb,b树只有三层,每个磁盘块最多15个data(黄)16个子节点(蓝),存满只有
16
×
16
×
16
=
4096
16\times16\times16=4096
16×16×16=4096
当4096条记录存满时,只能添加层数,层数的增加意味着io的次数增多,选择继续优化
B+树:
所有的数据都存放在叶子节点中,根节点和除叶子节点外的子节点只保留了下一个子节点的指针和key值
同样假设B+树只有三层,指针和键值占10byte,每个磁盘块可以有
16
×
1024
÷
10
≈
1600
16\times1024\div10\approx1600
16×1024÷10≈1600
1600个子节点,第三层因为存放的是value值行数据,所以假设行数据为1kb可以存放16条,那么存满可以放
1600
×
1600
×
16
=
40960000
1600\times1600\times16=40960000
1600×1600×16=40960000
40960000条记录,达到了千万级别的数据量
一般情况下,三到四层的B+树足以支撑千万级别的数据存储
在上述的描述中,影响最大的是指针和key值和的大小,这里面能控制的是key的值(索引类的值).key的值比较小的好,换句话说就是key所占空间的大小比较小的好,假设是int类型(占四个字节),如果是varchar可自己指定大小(假设只占3个字节),所以一般谁占用的空间小就可以使用谁作为索引.
2.3索引的维护
创建表的时候主键要不要自增?
在满足业务情况下尽可能自增
假设在上图B+树中插入了一条索引为14的数据,但是每个磁盘块只能存放4条数据,此时磁盘块5已经满了,需要对磁盘块进行拆分,拆成两个磁盘块,而每个磁盘块上需要有上层指针指向,如果恰好,上层磁盘块2也满了,那么,磁盘块2也要进行拆分,此时,数据插入时需要影响到上层的磁盘块.如果设置成自增,则只需要进行行末添加即可.
2.4聚簇索引,二级索引和联合索引
2.4.1聚簇索引
innodb存储索引中,数据在进行插入的时候,数据必须跟某一个索引列绑定在一起,如果表中有主键,那么跟主键绑定,如果没有主键,那么跟唯一值绑定,如果没有唯一键,那么选择一个6字节的rowid(隐藏的)进行绑定
数据跟索引放在一起的叫做聚簇索引,数据跟索引分开存储的叫做非聚簇索引myisam中只有非聚簇索引,innodb中既支持聚簇索引也支持非聚簇索引
2.4.2二级索引
一个表可以有几个索引?多个
每一个索引都是一棵B+树,还是所有索引公用一棵B+树?独立,也就是说一个表中会存在多个B+树
表中的数据存储几份?数据只存储一份,其他的非聚簇索引的叶子节点中存储的是聚簇索引的key值,索引innodb中也包含了非聚簇索引
例子:id,name,age,gender四个列
id是主键,name是普通索引
数据跟id绑定在一起(id对应的B+树是聚簇索引)
name对应的B+树的叶子节点中存储的是id,此时name就叫做辅助索引或者二级索引
如何确定innodb中的二级索引(辅助索引)?任何非聚簇索引都是二级索引(没有跟主键或唯一值或隐藏的6字节字段绑定的索引)
2.4.3联合索引(复合索引)
类似于联合主键的意思
一般情况下我们在设置索引列的时候只会选择一个列作为索引字段,但是在某些特殊情况下,需要将多个列共同组成一个索引字段,称之为联合索引
2.5索引优化
索引优化的时候需要注意什么?
- 索引字段要尽可能少的占用空间
- 在满足业务系统的需求内尽可能自增
- 索引字段尽可能不要为null
- 选择索引的时候索引的基数要尽可能大(唯一值/count>=80%的字段适合创建索引)
- 不要给所有的字段都添加索引,并不是索引越多越好
- 索引字段尽量不要频繁修改
- 尽量避免索引失效
什么情况下会出现索引失效?
- like查询的时候左边不要加%
- 索引字段上不要添加任何的表达式操作
- 索引字段在使用的时候不要出现类型的隐式转换
- 索引上不要出现函数计算
- 组合索引在进行使用的时候要遵循最左匹配原则
- in或者or在很多情况下回导致索引失效,但是要根据实际的情况来进行判断
- 在使用组合索引的时候,如果中间的某个索引列使用了范围查询,那么会导致后续的索引失效
2.6解释几个名词
回表
id,name,age,gender
id是主键,name是普通索引
select * from table where name = ‘zhangsan’;
查找过程:先根据name的值区nameB+树找到对应的叶子节点,取出id值,在根据id值去idB+树中查找全部的结果,这个过程称之为回表
回表的效率比较低,尽可能避免回表的产生
索引覆盖
id,name,age,gender
id是主键,name是普通索引
select * from table where name = ‘zhangsan’;
查找过程:先根据name的值区nameB+树查找结果,能够直接获取id和name,不需要去id的B+树查到数据了,这个过程叫做索引覆盖
索引的叶子节点中包含了要查询的全部数据,叫做索引覆盖
索引覆盖的效率高,推荐使用索引覆盖
最左匹配原则
id,name,age,gender
id是主键,name,age是组合索引,在查询的时候必须要从左向右查询
- select * from table where name = ? and age = ?;
- select * from table where age = ?;
- select * from table where name = ?;
- select * from table where age = ? and name= ?;(优化器会进行优化)
1,3,4会用到组合索引.匹配顺序在创建组合索引时确定
索引下推
id,name,age,gender
id是主键,name,age是组合索引,在查询的时候必须要从左向右查询
select * from table where name = ? and age = ?;
没有索引下推之前:先根据name的值从存储引擎中拿到符合条件的数据,然后在server中对age进行数据过滤
有了索引下推之后:直接根据name和age从存储引擎中筛选对应的数据,返回给server,不需要做数据过滤
将原来server层对age的过滤移到了存储引擎层,数据量少,io量少
如何回答面试官问题
- 实际场景
- 问题的情况
- 优化的方式和思考的过程
- 到达的效果
3.事务
3.1事务的特点
ACID:原子性,一致性,隔离性,持久性
3.1.1实现方式了解前提
日志系统:
binlog;二进制日志,数据同步以及恢复(server层)
undolog:回滚日志(innodb)
redolog:前滚日志(innodb)
errorlog:错误日志,操作出错记录
slowlog:慢日志,使用慢查询
relaylog:中继日志,主从同步使用
3.2各特点实现方式
3.2.1原子性:undolog
行记录,历史状态,类似于虚拟机中的快照
3.2.2持久性:redolog
io:随机io,顺序io
假设你在自己的村里开了一家饭馆,因为顾客一般都是左邻右舍,不会直接结账,需要赊账.此时你需要一个很厚的账本,里面写了赊账记录.一段时间后,写了很多人的赊账记录有几百页,此时,如果这天的客流量特别少,那可以慢慢地去翻这几百页把今天赊账的对应记录写进去,但是如果客流量十分庞大呢?再去找对应的记录就会十分低效.那么就可以在柜台前放一个小黑板,上面先记录着今天赊账的人的记录,等到晚上空闲的时候再往账本中的响应位置进行添加修改.redolog就是担任着这个小黑板的责任.
redolog是一个固定空间,会循环写
binlog和redolog都会同时记录数据,那么他们记录的顺序是什么样的?
当写完redolog,没有写完binlog的时候会有什么情况发生?
操作A数据库,B数据库是通过binlog来进行数据同步的.
当写完binlog,没有写完redolog的时候会有什么情况发生?
binlog记录了对应的数据,B已经将数据同步,但是redolog中没有
此处涉及二阶段提交
先prepare再commit
执行流程:
- 执行器先中引擎中找到数据,如果在内存中直接返回,如果不在内存中,查询后返回
- 执行器拿到数据之后会先修改数据,然后调用引擎接口重新写入数据
- 引擎将数据更新到内存,同时写数据到redo中,此时处于prepare阶段,并通知执行器执行完成,随时可以操作
- 执行器生成这个操作的binlog
- 执行器调用引擎的事务提交接口,引擎把刚刚写完的redo改成commit状态,更新完成
如果在写binlog之前断电,恢复后会将redolog的prepare置为失效
如果在写binlog之后断电,恢复后会将redolog的prepare置为commit
3.2.3隔离性:锁,MVCC多版本并发控制
并发情况:
- 读读:不存在任何问题,也不需要并发控制
- 读写:有数据安全问题,脏读,幻读,不可重复读
- 写写:有数据安全问题,可能存在更新丢失问题
为解决安全问题,可以通过加锁完成,但是加锁会导致效率变低,采用mvcc,但是mvcc还是只能解决脏读和不可重复读,幻读问题还是得加锁
3.2.3.1mvcc实现机制
了解mvcc实现机制要了解两个问题:隐藏字段和快照读
了解几个隐藏字段:
DB_TRX_ID 创建这条记录或者最后一次修改改记录的事务id (在事务操作的时候,事务的id值是递增的)
DB_ROLL_PTR 回滚指针,指向数据的上一个版本(回滚日志)
DB_ROW_ID 隐藏主键,如果没有显式主键的话,就会多一个隐藏主键
name | age | gender | DB_TRX_ID | DB_ROLL_PTR | DB_ROW_ID |
---|---|---|---|---|---|
zhangsan | 12 | man | 1 | null | 1 |
undolog:回滚日志,记录的是数据的历史版本
undolog中会形成一个链表,链首是最新的旧记录,链尾是最旧的旧记录
*purge的作用类似于垃圾回收器.
了解快照读:
首先了解:
-
当前读:数据读取的时候读取都是最新版本的数据
触发条件:select lock in share mode(读锁),select for update(写锁),update,delete,insert
-
快照读:读取的是历史版本的数据
触发条件:select
readview事务进行快照读操作的时候产生的读视图
主要有以下几个参数:
- trx_list readview生成时刻当前系统活跃的事务id
- up_limit_id 活跃列表中最小的事务id值
- low_limit_id 系统尚未分配的下一个事务id的值
可见性算法
根据readview会根据可见性算法,来判断是否可以读取到对应的数据结果
- 首先比较DB_TRX_ID<up_limit_id ,如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断
- 接下来判断DB_TRX_ID >= low_limit_id ,如果大于等于则代表DB_TRX_ID所在的记录在readview 生成后才出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断
- 判断DB_TRX_ID是否在活跃事务中,如果在,则代表在readview生成的时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明,这个事务在readview生成之前就已经开始commit,那么修改的结果是能够看见的.
大于等于小的(up_limit_id,up,但是是活跃列表最小值),小于大的(low_limit_id,low,但是是下一个事务id的值),不在列表中可见
例子一:
1,2,3分别为事务id;t1,t2,t3分别为时刻标号
在t3时刻select触发快照读,产生读视图,填充读视图如右图粉色框所示,按照可见性算法判断结果
*2已经提交了所以,2不是活跃的
2>=1进入下一个判断;2<4进入下一步判断;2不在活跃事务中,能够看见
例子二:
1,2,3分别为事务id;t1,t2,t3,t4分别为时刻标号
原先认知:
- 在t2时刻select触发第一次快照读,产生读视图,填充读视图如右图蓝色色框所示
- 在t4时刻select触发第三次快照读,产生读视图,填充读视图如右图绿色色框所示
实际上第三次的readview并没有重新生成,而是用的之前的readview
此时被改变的只有DB_TRX_ID,因为DB_TRX_ID不是在快照读里的,而是在隐藏字段中,不会受快照读影响
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ty5x5XfY-1658654145551)(https://s3.bmp.ovh/imgs/2022/07/24/f7f3888e970a66bf.png)]
2>=1进入下一个判断;2<4进入下一步判断;2在活跃事务中,不能看见
隔离基本有关问题:在RC隔离级别中,上述例子能看到修改后的结果,在RR隔离级别中不能看到
原因:在RC隔离级别中,每次进行快照读操作的时候都会重新生成新的readview,所以每次可以查询到最新的结果记录;在RR隔离级别里,只有当前事务在第一次进行快照读的时候才会生成readview,之后进行的快照读操作都会沿用之前的readview
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted 读未提交 | √ | √ | √ |
read committed 读已提交 | × | √ | √ |
repeatable read 可重复读 | × | × | √ |
serializable 序列化 | × | × | × |
概念 | 事务能够看到其他事务没有提交的修改,而其他事务又回滚了修改后的结果的情况被称之为脏读 | 事务能够看到其他事务提交后的修改,这时会出现一个事务内两次读取数据可能因为其他事务提交的修改导致不一致的情况,称为不可重复读 | 可重复读中可能出现第二次读读到第一次没有读到的数据,也就是被其他事务插入的数据,这种情况称之为幻读 |
3.2.3.2幻读问题产生的本质原因是什么?
如果事务中进行操作的都是快照读,那么是不会产生幻读问题的,但是当快照读和当前读一起使用的时候才会产生幻读问题
3.2.4一致性:
一致性是我们的根本追求,一致性的实现方式是由其他三个特点来保证的
参考视频:MySQL最全整理