当前位置: 首页 > news >正文

mysql 八股

行储存格式:

除大对象类型之外,一行的最大长度为65535(64kb)

变长列表记录变长列的真实长度。(他和null值的记录顺序都是逆序的)

null值列表标识当前行允许为空的列是否为null,空为1、否则为0。null值列表长度一定是8的整数倍

roll-pointer 上一个版本的指针(旧版记录在undoLog里)

trx-id 创建这条记录的事务id

真是储存时一个数据行最长16kb,超过16就会触发溢出,溢出的部分储存在溢出页中。具体的储存方式不同的记录格式有不一样的处理方法

注意,varchar(n)中的n指的是字符的数量而不是字符的长度

索引:

按照物理存储结构分为:聚簇索引(主键索引)和非聚簇索引(二级索引)

默认都是B+树实现

innoDB中B+的实现,每个节点都是一个页(16kb),页里面会储存很多个键值对,键就是索引的值,值就是下一个节点的地址,(判断下一个节点的方法是左闭右开)。非叶子节点(除最后一行外)只储存键值和地址,只有叶子节点储存具体数据。并且相邻叶子节点之间用双向指针连在一起,这是为了方便范围查找。页内查找比较通过目录槽优化(类似于跳表),而非简单遍历。

如果是聚簇索引(主键索引),那么叶子节点储存的就是一整行的数据,如果是非聚簇索引(二级索引),那么储存的就是主键索引的值。

使用二级索引的时候,如果要查询的内容不止二级索引的列,那么需要回表(根据查到的主键索引再查一次),如果只有二级索引的列,那就索引覆盖直接输出。

按照索引存储的数据类型分为B+索引,full-text索引和hash索引(InnoDB不支持hash索引)
按字段特性分为主键索引,唯一索引,普通索引和前缀索引

主键索引:

唯一索引

普通索引

前缀索引

前缀索引指的是用字符类型的前几个字符创建索引,而不是在整个字段上创建索引,好处是减少了储存每个索引值需要的空间,提升了查询效率

按索引的字段个数分为单列索引和联合索引

联合索引语句:

查询时先按照product_no查,一样了再按照name查。(最左匹配原则),如果查询的时候不按照这个写,会出现索引失效的问题(比如写where name = "1" and product_no = 2就会导致索引失效)。如果是联合了多个列的索引,那么执行的时候从最左边开始匹配,能匹配上的可以用索引,从第一个匹配不上的开始,后面就都用不了了

如果出现范围查询,那么范围查询的列可以用,后面的用不了。注意把范围部分的等于和范围部分分开来看,等于部分依然可以用索引,但范围部分就不行了

索引不是越多越好

1、占用空间    2、增加增删时维护索引的费用  

索引分布均匀的时候不要创建索引(比如性别,即大量出现重复的值),经常需要删改的时候不要创建索引(比如商城里的用户余额)

索引唯一时推荐创建、经常where是推荐创建、经常order group推荐创建

索引优化方法

前缀索引,为经常where到的语句创建覆盖索引,主键索引自增,最好非空,防止索引失效

索引失效
  1. 模糊匹配时使用左或左右模糊
  2. 对索引使用函数或进行表达式计算
  3. 联合索引非最左匹配
  4. 隐式类型转换要小心(mysql在比较时会自动把字符串转换成值,如果键是字符串,那么匹配时索引就会失效)
  5. 有or的话需要or两端都是索引列

事务

原子,一致,隔离,持久

脏读:读到了未提交的数据。不可重复读:对同一条数据两次读的结果不一致。幻读,满足select的语句增加或减少

读未提交不需要限制,串行化就每一行读写加锁,读已提交和可重复度依赖read view,只是创建read view的时间不同。可重复读的创建时间为事务启动时,读已提交创建时间为每个语句执行前,也就是说读已提交会创建多个read view。mysql默认隔离等级为可重复度。

MVCC:

read view(下称快照)储存四个值:创建快照的事务id(creator_trx_id),未提交事务的最小id(min_trx_id),本事务认为的下一个可以被创建的事务id(max_trx_id),未提交的事务列表(m_ids)

配合根据数据记录行中的trx_id就可以知道当前行记录是否这个事务可见的,如果不可见就要通过roll_pointer向undoLog中查找。判断可见性的逻辑就是看记录行的事务在创建这个事务时是否已经提交

mysql的可重复度隔离中,针对快照读(单纯select语句)使用MVCC,针对快照读(select...for update)使用next-key-lock加锁。这样可以很好的避免幻读(但不完全解决)。不完全解决指的是可能有特殊情况,比如事务B新增了一条记录,事务A对这个(对他来说)本不应该存在的记录进行更新,那么下次再读的时候他就能读到这个记录了。或者是先执行快照读,再执行当前读。

当前读:加锁,再读取最新的数据。加的锁是next_key lock。锁住的当前查询到的记录的前开后闭区间。比如:索引值为 102030 时,对 20 加 Next-Key 锁,范围是 (10, 20]。若查询条件为 WHERE id > 20,可能锁定 (20, +∞) 的间隙。

加锁语句(都要在事务中才有意义)
显式加锁:
select ..... for update  // 加行的记录锁或临键锁(排它锁)
select ..... lock in share mode   //加行的共享锁
lock tables ... write/read  // 加表的读或写错
flush tables with read lock // 全局锁
隐式加锁:
DML语句 (update insert delete)
update products set price=10 where restStore=1; // 加的是行级的X锁
DDL语句 (alter table, create index)
alter table user add column age int  //加的是表级的MDL
全局锁。

给整个库加锁,不允许再进行任何修改,为了方便全库备份(如果支持可重复读的话用mvcc也可以实现全库备份)

flush tables with read lock
表级锁

顾名思义,给表加锁。有表锁,元数据锁(MDL),意向锁和auto_inc锁

// 表锁
lock tables user read
lock tables user write
// 其他的锁不用显式加

元数据锁在crud的时候自动加,防止执行过程中表结构变化。

意向锁是在加行级锁的时候给表加的一个弱锁,方便给表加锁(不用遍历表的每一行来判断有没有锁了)

auto_inc锁 自增锁,主键如果被auto_increament修饰,那么主键就会自增。自增锁有三种强度:语句整体执行完才释放;自增完就释放;普通insert自增完直接释放+批量插入执行完才释放。使用轻量级的(自增完就释放)需要注意binlog设置避免数据不一致。

批量插入语句
insert into user 
values(),(),();insert into user (列1,列2) select 列A,列B from user1 where
行级锁

record lock, gap lock, next-key lock,插入意向锁

record lock,  next-key lock,可以使X或S锁,gap lock只能是S锁,插入意向锁只能是X锁

记录锁,顾名思义,锁住一条记录,有共享锁和独占锁  

select.... for update            

gap lock,锁住一个区间,别的事务不能在这个区间中插入内容。间隙锁之间是兼容的,设计这个锁的目的是为了防止区间中被插入(幻读)

next-key lock 也叫临键锁,锁住一个前开后闭区间,和gop lock锁住的范围不同,

插入意向锁,当一个事务试图向一个间隙插入内容事,会声明插入意向锁。如果间隙没有被锁住就可以操作。设计这个锁是为了并发场景下的插入,提高性能

日志

mysql的日志有三个 undolog redolog binlog。其中后两个是永久化到磁盘上的,undolog是内存中的,前两个是InnoDB层的,最后一个是server层的

undolog

主要用于事务回滚(保证原子性)和对MVCC的支持,记录的是更改前数据行的信息。不需要被显式写入磁盘,他的持久性依赖redolog实现

redolog

主要用于数据库的持久化(断电恢复,数据库崩溃等),记录更改后的数据,需要被写进磁盘中。写的时候可以选择写磁盘的策略,有三种:只写入缓存(buffer pool),什么时候写盘由操作系统决定;每次都先写入缓存中,每次提交事务的时候都将buffer Pool中的redolog写入到文件中并刷盘;每次写入缓存中,每次提交事务时将buffer pool中的redolog写入文件中(此时并没有写到磁盘中)。(注意文字描述的三种策略和图的三种策略顺序并不对应)

后台线程每隔一秒会将redolog buffer和redolog文件中的内容刷盘。mysql默认的刷盘策略是下图参数为1的情况。

写入策略有innodb_flush_log_at_trx_commit参数控制,参数为0和2的时候可能会出现刷盘处显示事务提交前的情况

redolog是循环写入,可以把它理解成一个圆,一边写一边擦除。只有这条记录对应的buffer pool中的脏页被持久化到数据库(刷新到磁盘中)了,这条记录才会被标记为可被擦除。可以把这理解为一个快慢指针。快指针就是写入的,慢指针就是被擦除的,如果慢指针追上快指针了,那么整个数据库就会被阻塞,停下来将buffer pool中的脏页刷新到磁盘中,才会恢复运行。

binlog

binlog用于主从库的复制和全局备份。它也需要被持久化到磁盘。和redolog不同的是,binlog是server层的日志(换句话说他不依赖于数据库引擎)。他的持久化过程也和redolog略有不同。每个线程会维护一个自己的binlog缓存,而不是维护一整个redolog buffer。具体的持久化有三种策略:每次事务提交线程将自己缓存中的内容写到binlog文件(此时依然没有写入磁盘),系统决定什么时候写入磁盘;每次提交事务线程都将缓存中的内容写如binlog文件并写盘;每次提交事务都写入binlog文件,攒够N个事务再一起写盘。mysql的默认策略是第一种(系统决定什么时候写盘)。

写入策略由sync_binlog参数决定

和redolog储存的是记录行修改后的内容不同,binlog有三种储存格式:储存修改数据的sql语句(statement),储存数据被修改后的实际内容(row),根据不同情况自动选择statement模式和row模式(mixed)

至此我们可以总结一下redolog和binlog的区别:1、首先适用对象不同:redolog只有InnoDB能用,但binlog所有的引擎都能用/2、其次储存内容不同:redolog储存的是数据的修改情况(比如那个表那一页多少偏移量的哪个地方坐了什么修改),binlog则有很多的储存方式。3、再有是两者的作用不同:redolog用于故障恢复,binlog用于主从复制,数据备份。4、最后就是写入方式不同,redolog是循环写,binlog是追加写。

主从库复制:

主库先把binlog写好,更新本地数据。从库创建一个io线程,和主库的log dump线程链接,接受binlog中的内容并写入relay log中,向主库返回复制成功响应。从库创建一个线程,读取relay log中的内容并更新从库数据。

根据数据库什么时候向客户端返回事务提交成功的响应,可以有三种主从复制方案:同步复制(所有库都复制成功并响应,主库才向客户端返回);异步复制:不等从库返回,主库直接向客户端返回,这就是默认的异步复制;半异步复制:等待一部分从库返回就,主库就向客户端返回。

事务两阶段提交:

为了保证binlog和redolog一致,采用事务两阶段提交。说人话就是先写redolog,然后将XA事务的id写入到redolog中(此时redolog是prepare状态),再写binlog(把XA事务的id也写到binlog中),写完binlog将redolog状态设置为commit。(redolog写入拆成了两部分)只要binlog成功写盘,那么就认为事务提交成功。

当出现问题时,就拿着redolog中的prepare的事务的XA事务id去binlog中找,找到了就说明已经刷盘成功了,将事务提交就行,如果没有找到,那就说明binlog没有写盘,需要回滚事务。

两阶段提交的问题在于会增加很多的磁盘io(redolog和binlog的提交都要单独刷盘),并且需要竞争锁。考虑组提交。组提交就是将多个binlog的提交合并成一次完成

redolog要到5.7版本才有redolog的组提交。

Buffer pool

缓冲池,数据库引擎自己的缓存。数据按页储存,储存数据页,索引页,插入缓存页,undo页,哈希索引和所信息

buffer_pool大小通过innodb_buffer_pool_size调节

数据库在加载的时候,实际上是直接将一整个页都加载到buffer_pool中,而不是只加载一条数据。

每个页有一个对应的控制块,控制块中储存了缓存页的地址,链表节点,页号和表空间

链表是为了更换的维护缓存页,增加缓存命中率。对于没有使用过的页(free页,空闲页),使用但没有发生修改的页(clean页,干净页),发生修改的页(dirty页,脏页)用不同的链表维护。free页是free链表,干净页用一个LRU链表,脏页用LRU和flush链表

mysql实际使用的并不是单纯的LRU链表,而是对LRU链表做的改进:分成了young区和old区,解决了缓存失效的问题(一次读取页的时候会读取当前页和当前页前后的内容,当前页前后一致没被用到但始终存在于缓存中就被称为缓存失效)。加入缓存时,页面会先被加入到old区,真正被使用且页面已经在old区留存了足够长的时间了才会被加到young区。(等待够足够时间才能被加入是为了避免一些伪热点页把真正的热点页挤掉)(伪热点页指的是哪些只被用了很少次数的页)

上面提到的等待时间又参数innodb_old_blocks_time控制(默认1)。young区和old区大小由innodb_old_blocks_pct控制(默认37,即37%)

数据库是先修改缓存中的内容,再将缓存持久化到磁盘。(即脏页被刷盘)。并且是先写日志,再刷盘(WAL,Write Ahead Log)

脏页会在以下几个情况刷新:redolog满了,buffer_pool满了,后台空闲了,数据库关闭了。

如果数据库抖动,说明脏页被迫刷盘,需要调大redolog的大小或buffer_pool大小


http://www.mrgr.cn/news/96689.html

相关文章:

  • C语言常用的字符串函数
  • 06-02-自考数据结构(20331)- 查找技术-动态查找知识点
  • 蓝桥杯 刷题对应的题解
  • Java基础 3.31
  • 【Feign】⭐️使用 openFeign 时传递 MultipartFile 类型的参数参考
  • SpringBoot详细教程(持续更新中...)
  • HCIP(RSTP+MSTP)
  • 记忆学习用内容
  • Sentinel[超详细讲解]-4
  • Axure疑难杂症:完美解决文本框读取、赋值、计数(玩转文本框)
  • 安卓一些接口使用
  • python文件的基本操作和文件读写
  • 实现在Unity3D中仿真汽车,而且还能使用ros2控制
  • Docker部署sprintboot后端项目
  • 【Golang】泛型与类型约束
  • 【第十三届“泰迪杯”数据挖掘挑战赛】【2025泰迪杯】【思路篇】A题解题全流程(持续更新)
  • 浏览器 ➔ 服务器or服务器 ➔ 浏览器:
  • linux进程信号 ─── linux第27课
  • Dubbo分布式框架学习(1)
  • (二)机器学习---常见任务及算法概述