读书后总结:挑选重点复习记录
目录
目录
目录
架构:客户端、服务器;服务器分为server、存储引擎、文件系统
字符集比较:
把字符集的对应的二进制比较
比如:如果是英文,大小写全转为大、小写在比较二进制大小
比如:如果是汉字,可以可以比较拼音首字母在比较二进制
utf8字符集默认比较规则是utf8_generral_ci,后缀_ci是不区分大小写
MySQL有四个级别比较规则:服务器、数据库、表、列。
服务器级别默认字符集utf8默认比较规则是utf8_generral_ci。(字符集与比较规则相关联)
数据库级别无默认,创建与修改数据库时可以指定字符集与比较规则,不可以通过半路修改变量改变当前数据库的字符集与比较规则。创建数据库不指定,不指定就用服务器级别的。
表级别默认:创建修改时可以指定。不指定则用数据库级别的
列级别默认:同一表中不同列可以分别用各自字符集与比较规则,创建与修改时不指定就会用表级别的。(列注:列存了汉字,该字符集为ASCLL会报错,因为表示不了,修改列字符集会改变列实际占用大小)
重要的字符集35页:
对于一个字符"我"来说,不同的字符集对于其编码方式不同:ASCLL没收录这个字符
ASCLL:128个字符(2的七次方):空格标点符号数字大小写字母一些不可见字符,统一用一个字节编码
GB2312:兼容ASCALL,所以编码方式有些奇怪有的用一个字符,有的用两个字符
GBK:比GB2312收录的汉字多一些兼容GB2312
UTF-8(也可以叫做字符集):是Unicode字符集的一种编码方案,还有UTF-16等其他编码方案,世界各个国家字符,还在不断扩从,编码方式采用1~4个字节,
MySQL的utf8和utf8mb4:utf8=utf8mb3用1~3个字符集表示一个字符。utf8mb4用1~4个字节表示一个字符额外可以表示表情符号。 utf8mb4在8.0是默认值字符集。
服务器与客户端通信时编码解码采用客户端操作系统的字符集,服务端解码后再转换成需要的字符集存入。
InnoDB存储引擎
行格式:最重要的就是COMPACT紧凑格式
行格式分为:真实用户数据、额外信息
额外信息分为:变长字段真实占用字节数列表、null值列表、记录头
变长字段真实占用字节数、null值列表都是逆序存放,因为指针位置指向额外信息和用户记录的中间、指针移动时正好是对称的
null值列表:允许为null的列(除了主键和notnull修饰的列)每个列用一个bit位映射,1=null,0=非null,且null值列表必须为整字节数存储,不足8的倍数,高位补0.(不可为null的列直接不存)
变长字段真实占用字节数列表:(变长包括:varchar、varbinary、text、blob)。比如列用ASCLL编码每个字符就固定位1字节,"aaa"该列就是3字节。如果个字段占用字节数特别多,就把溢出的部分放到溢出页中,变长字段占用字节数列表就只记录留在本页中内容的大小。且变长列内容为null的话直接不存。
↑我们在修改某个列的字符集,变长列表占用字节数也会发生变化。再说char类型。采用定长编码字符集时(ASCLL字符集编码固定一个字符一个字节,就叫定长。UTF的UTF-8用1~3字节表示一个字符,就是不定编码的字符集),char采用变长编码的字符集也会把实际占用字节数存到变长实际占用字节数列表中,
↑补充char(10)能放10个字符,当用ascll编码的字符集时,定长的字符集占用10字节,
当用utf8mb4占用10~40个字节,且char(10)至少要占用10字节(为了更新字段长度不大于10时直接原地更新,而不是重新分配空间),多余的空间用空格字符填充。且char类型字符集编码从定长编码变为变长编码,这个列也加入了变长字段实际占用字节数列表里。
逆序存放也为了提高,高速缓存命中率(因为靠前的字段与逆序存放列表距离更近)
记录头分为7个部分:
1、两个预留bit位(各1bit)
2、delete_flag:删除标志位,标记记录是否被删除(1bit)(0==没删除,1==删除)
3、min_rec_flag:B+ 树的每层非叶子节点中最小的目录项记录(目录项记录可以理解为非叶子页中的向下指向下一层索引的记录KV结构,k是主键列、V是主键列对应页号)都会添加该标记(1bit)
4、n_owned:一个页面中的记录会被分成若于个组,每个组中有个记录是带头大哥”,其余的记录都是“小弟”。“带头大哥”记录的n_owned 值代表该组中所有的记录条数,“小弟”记录的n_owned 值都为 0(占用4bit)2的三次方=8所以一组最大8条记录
5、heap_no:表示当前记录在页面堆(堆:每个页中的一堆记录)中的相对位置(13bit)(0和1被每个页中的上下确界早已占用,用户记录没增加一条就自增1,且每个记录此值分配就不会改变)
6、record_type:表示当前记录的类型(4种),0表示普通记录,1表示 B+树非叶子节点的目录项记录,2 表示 Infimum 记录,3表示 Supremum 记录(3bit)
7、next_record:表示下一条记录的相对位置(16bit)(里面存的数字代表下一条记录记录头和真实数据的中间位置的额字节数:向后找n个字节)
真实用户记录:写除了用户的可见列,还有三个隐藏列
row_id:行ID标识唯一1记录,不存在可当主键列时才有,否则没有
trx_id:事务ID:MVCC、锁都用到他。
roll_pointer:回滚指针,MVCC用
InnoDB数据页
一页默认16KB,
页是InnoDB磁盘和内存交换的基本单位。只有在初始化数据目录(文件系统)之前可以指定大小,运行过程中不能改变大小。
有的页存放索引、有的页存放undoLog
页有7个部分
1、文件头38字节
2、页头56字节
3、上下确界(infimum下确界最小记录、supremum最大记录)26字节
4、真正记录(包含记录头与用户记录)不确定
5、空闲空间不确定
6、页目录不确定
7、文件尾8字节
1、文件头:页的校验和、页号(唯一,但分配不一定连续)、双向链表的前后页号、页类型、属于哪个表空间
页类型:undo、索引页(数据页)、系统数据页、溢出页
2、页头:页的通用信息,页槽数,空闲空间开始位置,修改当前页的最大事务ID(仅在二级索引使用),当前页在b+数中的层级,当前页属于哪个索引,
6、页目录:页内记录是单向链表,记录太多不能从头遍历(curd都可用)(用槽二分法),页目录中有很多槽每个槽2字节,每个槽对应一个组,每个组中若干记录(包含上下确界、但不包含移动到垃圾链表的记录)
每个槽位记录组中最大记录的在页面中的地址偏移量(上一个组的最大记录next指向下一组的最小记录开始遍历),且是从尾部逆序存放(比如最小的是槽0是包含的下确界最小记录的组)。
每个普通组组中记录数4~8条,下确界所在组中只能有一条记录就是他自己
7、文件尾:检测页是否完整的刷新到磁盘中(与文件头的LSN字符串是否相同,刷回磁盘会按顺序最先刷回文件头,最后刷回文件尾)因为页是连续的存储空间
B+数索引
先列举 “列=常数” 的情况
且在记录数比较少只有一个页的情况
且主键索引列=常数的情况
流程:在页目录中使用二分法定位槽,在槽中的组(在前一个曹中找到该组)遍历组中记录
且非主键列(没有索引)那就得全表遍历了
现在且记录很多有很多个页
且“主键列=常数”
流程:这时候就要先定位页,在页中在定位记录,具体做法:先在根节点二分找到目录项记录,根据K主键找到V页号,一直找到最终的页,最终的页遍历的时候就找到了
补充:目录项记录的是下层每个页中的最小主键。
当页不够插入记录的时候在插入记录会分配新页,在如插入了小的主键,会先把大的主键移动到新页,小的主键在插进该页
重点补充:目录项记录的页不够存目录项记录也会页分裂,且这(非叶子节点的)页之间也是双向链表
我们3或4层B+数能存多少记录的算法是:先计算主键值大小,忽略页中文头,记录头等空间。用16k除(主键大小加指针大小)等于一个目录项的索引页能存多少,每个页中相当于嵌套了又一个页,依次相乘层数,最后一层乘的是每个真实用户记录页能存多少记录。
如三层:10000*10000*200;10000是目录项能存的数量,200是真实数据页能存数量
聚簇索引:
1、用记录主键值大小存储,排列记录与页。(包含内节点与叶子节点)
2、叶子节点存储完整记录各个列。
二级索引:
按照想建立索引的列排序,叶子节点存建立二级索引的列+主键值的K,V。
注意的是二级索引的内节点与聚簇索引的区别是内节点也要额外要存主键列(放在最后列比较)。且为了避免主键不同但是二级索引列完全相同,无法出现定位下层页的那个页。但是聚簇索引本身就不会重复内节点不会出现此问题
联合索引:
就是多个列建立索引,建立的规则:当靠前列相同值,按照下一列排列,依次如此(最后都相同就按照主键列,因为主键不能重复)。
唯一索引:
主键就是唯一的(且不能为null),如果是二级索引单纯设置UNIQUE,是可以存储多个null值的。
建立索引语句:
CRETE TALBE 表名(
各列信息,
INDEX 索引名字 (需要被索引的列)
)
ALTER TALBE 表名 ADD INDEX 索引名字 (需要被索引的列);
删除索引语句:
ALTER TALBE 表名 DROP INDEX 索引名字;
B+数索引使用
索引代价:
在聚簇索引发生改变,二级索引也要维护,二级索引是额外占空间的
注意:一条查询语句在执行过程中只能用到一个索引。
扫描区间和边界条件:
比如:id >= 3 and id <= 4 ;边界就是[3,4]查询时就是先定位到2这条记录,一直向后扫描一直到100结束。
[3,4]:扫描区间
id >= 3 and id <= 4:边界条件
索引选择:age in (1,2) or (age >= 38 and age <= 79)
整合上语句的各个扫描区间的边界条件:
[1,1]:单点扫描区间
[2,2]:单点扫描区间
[38,79]:范围扫描区间
补充:如果这个age是二级索引,二级索引找到一条就回表,又找到一条再回表。
不是所有条件都能生成边界条件:
key1 < 'a' and key3 > 'z' and 无索引列 = 'abc'
如果用key1索引:区间是(-无穷,'a') ; key3 > 'z' and 无索引列 = 'abc'就成了普通条件,需要回表在判断成不成立。
如果使用key3索引:区间是('z',+无穷);key1 < 'a' and 无索引列 = 'abc'就成了普通条件,需要回表在判断成不成立。
索引列常数使用一些符号可以形成边界条件:
=、in、not in、is null 、is not null、>、<、>=、between、like、!=、
in:产生多个单点扫描区间。
!=:如 key != 'a' ;区间是就是 (-无穷,'a') 和('a',+无穷)
like:匹配完整字符串或者、字符串前缀才形成扫描区间。后缀是普通条件不能形成扫描区间(百分号开头)
字符串比较大小:就是依次比较每个字符大小。如第一个字符串相同,比较第二个。这也是like前缀能形成区间的原因。
再说所有条件都能形成扫描区间时,如何提提取扫描区间。
key2 > 100 and key2 > 200
(100,+无穷)
(200,+无穷)
提取后变为 (200,+无穷)因为and取交
key2 > 100 or key2 > 200
(100,+无穷)
(200,+无穷)
提取后变为 (100,+无穷)因为or取并
有搜索条件不能生成扫描区间的时候
key2 > 100 and 无索引列 = 'a'
(100,+无穷)
(-无穷,+无穷)
提取后变为 (100,+无穷)因为and取交
key2 > 100 or 无索引列 = 'a'
(100,+无穷)
(-无穷,+无穷)
提取后变为 (-无穷,+无穷)因为or取交
这时候用索引变为全区间,每个二级都需要在回表。代价大于直接全表扫描。会直接选择全表扫描不走此索引。
从复杂的条件中提取区间
使用key1索引时:
用不到的其他索引和无索引列条件变为true=全区间
and true取交,可以去掉
or可以去掉false的结果,中间的正好是false
最后两边字符串的比较规则取并就是
最终区间为:(‘xyz’,+无穷)
使用联合索引时的区间:
先说明联合索引的排序先按照列1排序,列1等值的看列2排序依次如此最后按主键。
key1 = ‘a’ and key2 = ‘b’
这时候会定位到 列1所有等于a的记录中依次向后找一直到 key2列等于b位置,这就是起始位置。
索引条件下推
在使用二级索引且是联合索引且key1 = ‘’ and key3 =''
这种情况key3会失效(key不能够在继续减少搜索边界了)。因为无法通过key2等值再找key3
避免不满足key3的列也会回表(回表找页IO)。Server直接把 搜索条件下,推给存储引擎, 在存储引擎中,回表前!在索引这条记录的这个字段直接pass掉不等于key3的条件。
索引可用于分组与排序。
但注意写法。
分组原理比如:key1 分组。key1等值的都为一组,
排序原理:索引本身就有组组织的。
文件排序,索引排序与索引排序SQL注意事项
在内存、或者内存不够大在磁盘中的的排序叫文件排序。
如果使用索引排序就不用额外文件排序。
比如,order by key1, key2,key3.
直接就是有序的。
SQL注意:
正确:where key1 = ‘' and key2= ‘’ order by key3
补充:desc降序也可用到索引。不过很复杂,每个槽比如有8条记录就要通过前一个槽遍历7 次(不是8是因为直接下一个槽的大哥就是最后一条记录)。
1、不用记:不能正序降序一起混用:order by key1 ASC, key2 DESC。使用索引的效率不如不用。
因为8.0支持正序降序一起混用。
2、不是同一个索引的排序不能使用索引。
order by key1 ,index2.因为不能保证key1等值的时候,按照index2另一个用不到的索引排序。
直接都用不了。
3、联合索引不连续:order by key,key3. 直接都不能用
4、形成扫描区间的列不能用索引排序:where key1 = ‘' order by key1
5、列明用了函数 order by fangction( key)
索引用于分组:
正确用法:group by key1 , key2 , key3 分了三次组。
如果没用上联合索引会生成临时表。
具体的错误做法没有示例。
补充:当回表记录太多优化器倾向于全表扫描。limit10之后倾向于二级索引+回表
创建索引原则
用于搜多条件、分组、排序的列建立索引。
索引区分度小不适合建立(但是不绝对,例子忘了带补充)
列类型尽量小,tinyint(1字节)、mediumint(3字节)、int(4字节)、bigint(8字节)这个建议更适用于主键。
字符串类型建立索引的话建立前缀索引。key1(10)10代表字符数。
覆盖索引
当需要查询的列在二级索引中都包含了,就不用回表了。*一定会回表。
索引列要在语句中单独出现,不要列与运算符用在一起。
插入主键主键是是不是递增的效率:
一定要递增:不然插入主键忽大忽小,页满了就要页分裂。
表连接原理(生产中一般不join,不如多看看排序)
join两个表:
本质上不加条件会产生笛卡尔乘积。两个表中每条记录都与另一个表连接一遍。
所以我们要join时添加条件,条件分为两种:
1、单表条件:t1.id >1 、 t2.id > 2
2、两表条件:t1.id = t2.id
连接过程:
确定驱动表。
在驱动表中找到符合的每一条记录(单表条件)都去从表里匹配(两表条件)
注意:驱动表只访问一次。从表访问n次。
连接分为内连接与外连接。外连接分为左外连接。与右边外连
左外连接,左表是驱动表。右边外连接,右边是驱动表。
先用on再用where的效果。
外连接时on不符合的记录也会加入结果集
where不管什么连接都要符合才能加入结果集。
连接算法
(不用记)笨重的:嵌套循环连接。
因为从表要访问多次。可以在从表加索引。
最好的:块的嵌套循环连接。
把驱动表多条记录当做块。从表的访问次数就变少了。
这个块大小叫joinbuffer,可以适当调整大小。默认为256k
这个块并不会存所有列,只存需要返回的结果集列和两表连接用的条件列。
MySQL对用户的查询语句重写操作。
EXPLAIN执行计划
执行计划在CURD语句都可以用都是在语句前加EXPLAIN关键词
执行计划各个列:
id | 每个select关键字都对应一个唯一id |
select_type | select关键字对应的查询类型 |
table | 表名 |
type | 针对单表的访问方法 |
possible_keys | 可能用到得索引 |
key | 实际使用的索引 |
key_len | 实际使用的索引长度 |
ref | 当使用索引列等值查询时,与索引列对象等值匹配的对象信息 |
rows | 预估需要读取的记录条数 |
filtered | 针对预估需要读取的记录,经过条件过滤后剩余记录的百分比 |
Extra | 一些额外信息 |
table:表示这一行的小查询是针对哪个表,可能是具体的表,可能是临时表
id:比如子查询就有两个select(每个select有唯一的id)、union也是,虚拟表id为null
select_type:每个小查询在整个selec中扮演的角色,
简单查询 | SIMPLE | 不包含联合查询、子查询 |
主查询 | PRIMARY | 联合查询、子查询中的主查询(唯一) |
联合查询外 | UNION | 联合查询除了主查询外的 |
联合合并结果 | UNION RESULT | 联合查询中合并结果的 |
子查询 | SUBQUERY | 第一个子查询,不依赖外部查询 |
依赖子查询 | DEPENDENT SUBQUERY | 第一个子查询,依赖外部查询 |
依赖联合查询 | DEPENDENT UNION | |
虚拟表 | DERIVED | |
物化 | MATERIALIZED | 物化子查询 |
type:对单表的访问方法
系统 | system | 表中只有一条数据 |
常亮 | const | 根据主键、唯一二级索引列,与常数匹配 |
唯一与常量等值 | eq_ref | 表连接时从表是根据主键或者不能null的唯一二级索引,等值匹配(两表条件,所以不是与常量) |
普二与常量等值 | ref | 查询或表连接从表普通二级索引列,与常量等值匹配。 |
可null普二与常量 | ref_or_null | 与可能为null的普通二级索引列与常量等值匹配。 |
索引合并 | index_merge | 用到了三种索引合并方式。此时实际用的索引就不止一个。 |
in转子查询与非null唯一 | unique_subquery | 针对包含in可以转为exits时用上了,主键或者不能为null的唯一二级索引,等值匹配 |
in转子查询普通 | index_subquery | 与上,唯一不同是子查询使用的是普通索引 |
范围 | range | 条件是单点区间或者是范围区间 |
索引覆盖 | index | 可以使用索引覆盖但是需要扫描全部的索引(类似于二级索引失效) |
全表扫描 | ALL | 全表扫描 |
key_len:单位字节。涉及多少个列充当形成扫描区间的条件。当我们决定用哪个索引来查询时,可以通过它直接看到扫描区间边界条件
注意该索引列可以使用null则要+1,如果是变长类型还要在加2,然后就是实际占用字节数
比如int固定的 4字节,varchar(100) utf8-mb4字符集就是 100 * 4;=403
然后计算这个长度是一个列的,所以联合二级索引使用了一个列。
如果key_len值为806那就是两个列。
ref:单表访问与索引列进行等值匹配的是什么。
可能是const常数、可能是null、可能是列的全限定名,可能func函数。
rows:
当全表扫描时就是该表的预估行数
当用索引时就是扫描索引的记录行数,满足索引列条件的只有n行。
filtered:
使用索引时,在满足索引形成的扫描区间的行数里满足其余条件的行数百分比是多少
主要是的意义是:在表连接时,看被驱动表还要访问多少次。
No tables used | 没有from子句 |
impossible where | where子句永远为false |
No match min/max row | 当查询列表、、、、、 |
Using index | 使用覆盖索引 |
Using index condition | 使用索引条件下推 |
Using where | 条件需要在Server判断 |
Using filesort | 造成了文件排序 |
Using temporary | 使用临时表 |
事务
数据库事务要从原子一致隔离持久四个特性来解释
保证原子一致隔离持久性的一个或多个数据库操作称为事务
原子性:一些不可分割的sql,要么全做,要么全不做。
一致性:事务执行后的数据要符合,业务逻辑约束。
隔离性:多个事务之间影响的级别。
持久性:事务提交,改变的数据应该被磁盘永久保留。
事务的状态
正在活跃:正在活跃
失败:遇到了错误或者手动停时,变为失败状态。
中止的:回滚操作完成变成已回滚。
部分提交:数据库最后一个操作执行完,改变的数据在内存中还没有刷新到磁盘。
提交的:已经完全刷到磁盘了
事务sql语法
开启事务:start transaction
开启只读事务:start transaction read only
开启读写事务:start transaction read write
开启一致性快照读事务:start transaction with consistent snapshot
提交事务:commit
手动回滚事务:rollback
注意:事务执行过程中,大部分会回滚失败的语句,遇到死锁会回滚整个事务。
系统变量自动提交:
查看自动提交变量:show variables like ‘autocommit’;on是开,这时候每条语句单独是个事务。
设置自动提交变量关闭:set autocommit = off
注意隐式提交:
自动提交变量为off时,在一个sqlSession中:语句开始事务-》执行语句-》最后没写提交语句,直接写DDL、又开启一个事务、修改表结构语句、等等会直接隐式提交。
回滚到保存点而不是回滚整个事务;
定义保存点:savepoint 保存点名称
回滚到保存点:rollback to 保存点名称
redoLog
redoLog保证事务持久性
基础知识:随机IO慢。顺序IO快。(修改的数据的页并不一定相邻,刷回磁盘就会随机IO)
我们在修改多个页中的数据,每个页就修改一个数据,这样刷回整个页是不划算的。InnoDB做法是读取页后不着急刷回数据页,而是把改动存在数据存在redoLog里,redoLog刷回磁盘就好。,redoLog刷回磁盘就算事务提交成功。
用redoLog的做法保证持久性的好处:redoLog小,只有除了更新值外的表空间Id、页号、偏移量额外信息。而且redoLog是按照产生顺序挨在一起的,刷回磁盘的时候是顺序IO。
redoLog格式:
针对事务对数据库不同的修改场景定义了53种redoLog类型
绝大部分的结构是:
type:53种redoLog类型
spaceID:表空间ID
pageNumber:页号
data:修改内容:页中偏移量位置修改的啥。
基础知识:全局变量隐藏列row_id主键赋值不是每次自增1都刷回(避免频繁刷盘),而是在256的倍数的时候在刷回。启动系统的时候,把磁盘中这个变量加256就可。
具体redoLog类型:
简单一些的:
MLog_1byte:(type = 1)在某偏移量写1字节。
MLog_2byte:(type = 2)在某偏移量写2字节。
MLog_4byte:(type = 4)在某偏移量写4字节。
MLog_8byte:(type = 8)在某偏移量写8字节。
比如:全局变量隐藏列row_id主键8字节,我们就可用(type = 8)来存储。
复杂一些的:
insert数据时,可能会更新系统页,和用户数据页(用户数据页,包含主键和二级页)页中的槽信息。
比如,插入一条数据,更新row_id值,索引数内节点页,叶子节点页,和这个表的二级索引数的页、还可能会创建新页,或者页分裂的新页。页中还有页槽,页头,页目录
这种情况↑,页中的许多信息都要修改,如果每个修改的地方都分别记录一个redoLog可能最后与会比原本数据都大,所以采取把页中从第一个修改的位置到最后一个修改位置的数据当做一条redoLog来记录。
MLog_rec_insert:(type = 9):插入使用非紧凑行格式记录时redoLog类型
MLog_comp_rec_insert:(type = 38):插入非紧凑行格式记录时redoLog类型
MLog_comp_page_create:(type = 58):创建一个紧凑行格式的页
MLog_comp_rec_delete:(type = 42):删除一条使用紧凑行格式的页
MLog_comp_list_start_delete:(type = 44):删除使用紧凑行格式页中一系列行。
如果系统崩溃,使用特定函数,根据redo日志回复数据。
redoLog组(MRT):一些不可分割的原子redoLog,比如插入一条数据,页分裂,那么两个叶子节点的页redoLog和内接节点的页redoLog,这些redoLog恢复是不可分割的整体。
恢复时MRT要原子处理,解析到(type = 31)的redoLog视为解析到了一组结束。
所以事务与redoLog包含关系为:
一个事务包含若干语句
一个语句包含若干redoLog组
一个redoLog组包含若干redoLog
基础知识:redoLog组(MRT)的日志存在页512Byte大小的页中。(普通的redo也存这里)
基础知识:redoLog页中也有页头页尾,MRT是存在body中
基础知识:机器启动默认16KB,申请连续内存的buffer pool 存储 redoLog组
基础知识:一组的redoLog,全部产生完,在一起写到 redo的 buffer pool
redoLog刷盘时机:
1、buffer pool占了一半时。
2、提交事务时
3、脏页(数据页)刷回磁盘之前,要保证这个页对应的redoLog先刷回。
4、后台线程每秒刷回一次
5、关闭数据库
基础知识:每个redo buffer pool 大小对应一个redo 文件,文件不止一个他们是循环链表的写入方式,有个检查点的概念,因为有循环写有追问题。
基础知识:每个文件前四个redo页存管理信息。
LSN:日志序列号,初始值是8704(已经写入redo的日志量,单位字节)
每个MRT都有LSN值,值越小产生时间越早。(LSN值增长单位是字节数,包含日志,包含页头页尾也要算进去)也就是一个MRT如果很大,可以跨页,可能是为了方便buffer free指针。
崩溃恢复过程:(待复习)
1、取出最近发生的那次检查点信息
2、从检查点日志序列号组中对应的偏移量开始一直扫描block页中的值不为512K为止
3、恢复过程使用Hash表加快恢复过程,并且跳过已刷回磁盘的页面
undoLog
undoLog主要用于事务回滚:
undoLog可以保证事务原子性原子性。
在事务里增删改的时候都要额外记录,查询不会生成undoLog
基础知识:事务可以是只读事务,也可以是读写事务。
只读事务不能对表增删改(但可以对临时表增删改)
基础知识:事务ID全局唯一且自增1,这个变量自增256的整数倍的时候刷盘在5号页的最大事务ID属性中。这个属性占8字节,系统启动把磁盘中最大事务ID加256.
只读事务第一次对临时表增删改是分配事务ID。
读写事务第一次增删改的时候分配事务ID
undoLog格式
当增删改记录时,要记录一个undolog,更新操作可能会产生两条undoLog
基础知识:undoLog从0开始编号,每条依次递增
endOfRecord | 本条undoLog结束,下一条开始时在页面中的地址。 |
undoType | 本条undo日志类型,比如trx_undo_insert_rec |
undoNo | 本事务中,本条undoLog编号 |
tableId | 对条日志对应记录所在表的表Id |
主键各列信息 | 主键每个列占用的存储空间大小和真实的值(一般主键都是单列,不会联合主键,联合主键的话联合主键的各个列都是要记录的) |
startOfRecord | 上一条undo日志结束,本条开始时在页面中的地址。 |
基础知识:insert时,把记录创建好,然后记录的回滚指针指向创建这个记录的InserUndoLog,
基础知识:undoLog记录是针对聚簇索引
insert时候不仅在聚簇索引插入一条数据,和这个表的所有二级索引也都插一条。insert的undoLog只要记录一条,且记录主键信息就好了。这样回滚删除的时候可以根据主键信息删除对应的聚簇索引和二级索引。
roll_pointer:回滚指针,指向某条undoLog起始位置。
delete对应的undoLog:
基础知识:页头中有页Free属性,指向垃圾链表头节点。
没个记录行的已删除标志属性为1,就查不到了,移动到垃圾链表就真删除了。
两个阶段:已删除标志属性为1,删除标记。(在删除这条记录的事务提交前一直处于中间状态)
这种中间状态为了MVCC,删除这条记录的事务提交后,有专门线程来把这条记录移动到垃圾链表。就真正删除了。阶段2也叫purge
endOfRecord | 本条undoLog结束,下一条开始时在页面中的地址。 |
undoType | 本条undo日志类型,比如trx_undo_del_mark_rec |
undoNo | 本事务中,本条undoLog编号 |
tableId | 对条日志对应记录所在表的表Id |
rtx_Id | 旧记录的事务ID |
roll_pointer | 旧记录的回滚指针 |
主键各列信息 <len,value> | 主键每个列占用的存储空间大小和真实的值(一般主键都是单列,不会联合主键,联合主键的话联合主键的各个列都是要记录的) |
索引列各列信息 <pos,len,value> | 针对聚簇和二级索引,pos是什么位置?,从0开始算。 |
startOfRecord | 上一条undo日志结束,本条开始时在页面中的地址。 |
基础知识:每条undoLog保存旧记录的undoLog,这样可以根据本条找到上一条undo改动。insert就是最新的记录没有旧版本所以不用记回滚指针,也不用记事务ID,
举例子:在一个事务中我们先创建一条记录,在删除这条记录,undo的版本链就是。中间状态的记录(创建的这条),被delete_mark,中间状态记录回滚指针指向,deleteUndo,deleteUndo的回滚指针,指向最初的insertUndo.
update对应的undoLog格式
基础知识:update的UndoLog分为更新主键和不更新主键,
不更新主键分为:就地更新(更新前后大小不变),先删除(加入删除链表这次由用户线程来做)旧记录在插入新记录(更新前后大小有变化)。
endOfRecord | 本条undoLog结束,下一条开始时在页面中的地址。 |
undoType | 本条undo日志类型,比如trx_undo_upd_exist_rec |
undoNo | 本事务中,本条undoLog编号 |
tableId | 对条日志对应记录所在表的表Id |
rtx_Id | 旧记录的事务ID |
roll_pointer | 旧记录的回滚指针 |
主键各列信息 List <len,value> | 主键每个列占用的存储空间大小和真实的值(一般主键都是单列,不会联合主键,联合主键的话联合主键的各个列都是要记录的) |
n_update | 本条update语句执行后有几个列被更新 |
被更新列更新前的信息LIst <pos,old_len,old_value> | 位置,长度,值 |
索引列各列信息 List <pos,len,value> | 针对聚簇和二级索引,pos是什么位置?,从0开始算。 |
startOfRecord | 上一条undo日志结束,本条开始时在页面中的地址。 |
注意:update更新包含了的索引列,才会加入索引列各列信息List。
注意:update语句执前,先生成updateUndoLog
举例子:开启一个事务,插入主键为1和2,两条记录,在删除主键为1的记录,在更新主键为2的记录。
版本链为:id=2的记录回滚指针指向updateUndoLog,updateUndoLog回滚指针指向。insertUndoLog,insertUndoLog没有回滚指针没有上一个版本。
update更新主键。(产生两条undo日志,删trx_undo_del_mark_rec、插trx_undo_insert_rec)
更新主键这条记录的位置就发生了改变,
update更新主键步骤:
步骤1、先deleteMark原记录(事务提交后由purge线程加入垃圾链表)只deleteMark是为了,别的事务能够在本事务提交之前可以访问到这条记录。(如果直接加入到垃圾链表,别的事务就访问不到这条记录)
步骤2、根据更新后各列的值创建一条新记录,并插入到聚簇索引(根据主键更新后的值)。
增删改操作对二级索引的影响
insert和delete影响差不多,但是update不同,如果update不涉及二级索引列,就不用对二级索引执行操作。
基础知识:只有聚簇索引叶子节点才有事务ID,回滚指针。但是,每次增删改一条二级索引记录都会影响二级索引页页头中的页最大事务ID属性(记录修改本二级索引页的最大事务ID)。
undo日志写入那里,应该注意什么?
装聚簇索引和二级索引的页类型 | fil_page_index |
装undoLog的页类型为 | fil_page_undo_log |
一个事务有若干语句,一个语句对若干条记录改动,每条语句可能对应1或者2条UndoLog | 所以一个事务产生的undoLog一个页可能装不下,就用多个页装,不同页之间双向链表 |
undoLog大类型有两种 | insert(针对插入,因为事务提交对应插入日志就可删除)和update(更新和删除) |
同一个UndoLog页不能混着存两种大类 普通表和临时表产生的UndoLog要分别记录 | 所以一个事务中最多四个链表(聚簇索引的两大类,加临时表的两大类) |
undo日志具体写入过程