《MySQL + Innodb存储引擎 》三刷总结

第一章

1 . 数据库指的是 二进制文件,而数据库实例是操作所有数据的程序。
2 . MySQL的整体结构从下到上 : 物理的二进制文件(不同的文件系统分卷形式等) ----- 针对表的存储引擎 ----- 优化器组件、缓冲组件、管理服务与工具组件、SQL接口组件 ------ 连接池组件 ----- 不同语言的对接接口
3 . 针对单表建立一个 .ibd 文件,每一个数据库 里面单独分成了一个文件夹,该文件夹下面是所有的表文件及 .frm 文件.
4 . 使用 MVCC 针对一个数据,如果一行数据已经被加 X 锁了,可以利用 undo日志还原出它之前的版本值,让另一个事务去去读之前的版本值,获得高并发性.
5 . 四种隔离级别解决的问题以及采用的策略、insert buffer , double write , 自适应哈希索引, 预读取(后面)
6 . 数据的通信本质上是进程之间的通信,MySQL有TCP ,共享内存等方式,所以在JDBC中的通信也是首先建立一个进程通信,然后在进程里面不断的通过现场发起操作。

.
.

第二章 innobd 概述

Checkpoint :

首先是为了防止缓冲刷新失败,数据丢失,事务提交时,采用了 先写redo日志,再修改页的方法。是为了避免所有的数据都缓存在内存中,然后当电脑宕机的时候,数据丢失,虽然可以从redo日志文件恢复,但是数据量过大,checkpoint 即表示在checkpoint 之前的数据,都是已经被刷新到了磁盘的。
缓冲池不够是,执行一次checkpoint。
Redo日志重用,会执行checkpoint
通过LSN log sequence number 来标识最新的checkpoint
Sharp checkpoint 执行在数据库关闭的时候
Fuzzy checkpoint 执行在
1 . master thread 周期性的异步刷新缓冲池的脏页到磁盘中
2 . LRU列表,需要检查列表是否足够空闲页,然后发现脏页则执行checkpoint(一个单独的 page cleaner 线程执行检查可用页数量 )
3 . Async/ Sync flush : redo 日志已满,需要将剩余 checkpoint 之后的所有日志及相关的页,全部刷新到 磁盘中。然后redo日志可以全部复用了。剩余checkpoint 之后的 是有一个阈值的,不同的情况下不同。主要也是为了保证 redo 日志文件的循环使用。 放在了 cleaner page thread 中。
4 . 脏页数量达到阈值,强制执行一次checkpoint

补充 :
LSN : 写入 redo 的字节数
1 . 并不是每次的脏页刷新都会执行 checkpoint ,因为在checkpoint 之前的数据是不需要 重做的,而且checkpoint 小于等于 redo 的LSN。一次强制的 checkpoint 应该会更新所有页头部的 page_LSN 序号到最新的 checkpoint。而一次脏页刷新应该是将 page_LSN 更新到 redo的最新的 LSN 。
.
2 . 关于他们的优先级 : redo 第一,redo理论上在事务开始是就开始了(分布式里面也有prepare的操作),然后一定的周期执行 checkpoint ,一旦数据库宕机,根据页头部里面的 page_LSN 序号以及 checkpoint 和 LSN 来判断是否需要进行重做,因为这里 页的 page_LSN 值可能是 上一次脏页刷新后更新的LSN 或者 执行了checkpoint 的LSN,那么如果 page_LSN 的值小于 redo 的LSN ,那么说明需要进行重做,但是redo里面在 checkpoint之前的不需要进行重做了。
.
3 . 一个问题: 那么在宕机前,没有在缓冲池里面的 - 磁盘里面的那些页,它们的page_LSN 值在宕机后理论上也是小于 redo 的LSN 的,因为 redo 已经写入了。那么怎么确定它们不需要重做?????我觉得 通过LSN 来判断有漏洞 ??? 并且redo里面记录的肯定存在 space_id ,page_offset ,可以直接定位到是哪一个 页,然后checkpoint之后的不重做不就OK了吗 ?????

Master Thread :

内部多个循环 :主循环 loop ,后台循环 back loop ,刷新循环 flush loop ,暂停循环 suspend loop。Master根据数据集状态在不同的循环里面切换

main loop

在 master 中有每秒进行一次的: 日志缓存刷新到磁盘即使事务还没提交、可能存在合并插入缓存、没有用户活动则切换到 backloop 、 每秒可能刷新一定量的 脏页到磁盘。
每 10 秒进行一次的:刷新脏页、合并插入缓冲、日志缓冲刷新到日志文件、删除无用逻辑undo页

bask loop

删除无用的逻辑undo页、 合并插入缓冲、跳回 main loop

flush loop

刷新脏页

suspend loop

没有事情了,到挂起状态

关键特性

插入缓冲

关于缓冲:
在缓冲的层面上: redo日志缓冲 ----- 额外内存池 ------ 缓冲池
缓冲池里面 : 数据页、索引页 、 插入缓冲 、 锁信息 、 自适应哈希索引 、 数据字典信息
关于索引:
关于数据存储是在一个数据页里面,然后针对主键创建了 B+ 树,一个表的数据可能有很多的数据页,数据的分页也是按照主键进行区分的,所以 查找的时候是相对的顺序的,速率很高。
但是针对 辅助索引(其他字段显示的建立了索引),也会有一个索引树,但是 存放仍然是按照主键顺序存放的,但是这样按照 辅助索引进行查找的时候,访问是乱序随机的,可能一次在这个数据页,一次在另外一个数据页,所以效率不高。
.
针对的情况 : 表中是按照主键进行顺序存放的,所以在 按照主键进行查找更新操作的时候,很多的访问都是顺序的,按照辅助索引很多的都是随机访问。所以 加入一个 insert buffer ,即将这些针对辅助索引进行的操作缓存起来,然后用后台线程 在查找的时候,看哪一些的操作 是针对叶子节点的同一个 数据页的,然后将这些操作一次性的进行。
每个表都有一个 唯一的 space id。
每个页 也有 page_no

double write

因为 页 是 16 K ,文件最小是 4K ,而磁盘IO 最小是 512 Bytes,所以 会存在部分写失效的情况,导致那脏页的16K 数据丢失,并且磁盘的数据 损坏,而 redo log(后面) 是记录 磁盘的物理操作的log (最小IO 也是512),但是已经写入了部分,导致对应页的损坏,无法使用redo log 进行恢复。
所以 在将 脏页 刷新到 磁盘之前,第一步 首先 拷贝到内存 的 double write buffer (2M),第二步 将2M 两次写入 共享表空间(2 M),第三步 将buffer 的脏页刷新到 磁盘。如果宕机损坏,则从共享表空间拷贝,恢复最新的数据。

自适应哈希索引

对于某些热点的查询,可以针对 索引 值建立哈希索引,然后不需要经过 B+ 数的几层索引查询,则直接定位到数据页及偏移。
哈希索引只能用于 等值 查询。

异步IO

不需要等待执行完毕
可以合并多个IO ,比如页是连续的,会一次IO从磁盘读48K。

刷新邻近页

刷新脏页的时候,检测该页所在区(64个页)是否有其他的脏页,有则一起刷新,通过AIO,合并IO,减少磁盘 IO 次数。
存在的问题:不怎么脏的页,刷新后变成脏页,需要再一次从磁盘去获取???反而是负担。

以上的总结 :
从磁盘 到 文件 到 页 的 一次 IO 最小粒度是不同的。会存在 部分 写的问题,所以需要保存一个副本在共享表空间里面。防止数据损坏。
针对辅助索引需要建立插入缓冲,合并多个针对同一个数据页的操作,减少随机离散访问次数。
针对热点的查询建立哈希索引提高效率。
异步IO 可以检测到页的操作,判断是否连续,是否针对同一个页,然后从磁盘进行IO的时候,可以一次性读取到内存中。
.
.
.

第三章 文件

是文件,存在文件系统中的,最小的 IO 是4k。

参数文件

日志文件

错误日志

记录整个操作中的错误报告

慢查询日志 slow log

将 SQL 超过时间阈值的记录下
将 没有运行 索引的SQL记录下
数据库前期,查询时间都短,引入 逻辑读取 与 磁盘物理 读取次数两个特性判断SQL是否记录

查询日志

所有的请求都会记录,无论成功

二进制日志

二进制日志,主要记录的是对数据库表的更新操作
有不同的格式,默认格式 statement 为 直接记录 SQL 语句,然后从数据库库通过SQL更新
其他格式 : ROW ,记录的是表的对每一行的更改,选择不同的隔离级别,并发性更好

套接字文件

pid文件

只存放了 数据库实例 当前的进程ID,每次重启会更新

表结构的定义文件

在数据库文件夹下 : 针对每一个表都会有与之对应的 .frm 文件,存储表的结构。数据存在数据库文件夹下面的 .ibd 文件.
在这里插入图片描述

存储引擎文件

在这里插入图片描述

表空间文件

共享表空间文件 ----- && ----- 独立表空间文件(.ibd 记录 数据、索引、等)

redo log 重做日志文件

与 checkpoint 相关
每次写入磁盘的大小是 512 kb,所以一定成功,不存在部分写。
重做日志的条目格式:
redo_log_type ---- sapce ------- page_no ------ redo_log_body
类型 ----- 表空间ID ---- 页的偏移量 ---- 数据
具体记录的什么????以及 为什么说 redo 是记录 的 物理操作。 其他的日志记录的是 逻辑操作。
–看了后面的解答 : redo 记录的实际是一个先于实际页操作的理论页操作记录,我觉得可能是对缓冲池里面的页的操作记录,而不是记录硬盘的页的物理操作,因为 首先需要保证 redo 的原子性与 持久性,才能保证在宕机后,咩有刷新到磁盘的脏页也可以通过 redo 进行恢复,后面说明了 分布式的情况下有不同的地方,但是先于所有的都是redo,redo是最重要的保障。

.
.

第四章 表

索引组织表

每个表都会有一个主键,都是按照主键索引顺序存放数据的,如果没有显示的定义主键
1 . 非 null 的 unique 的第一个 字段定义为所以
2 . 不存在的话,引擎自动创建一个6字节的指针作为主键形式的存在

引擎的存储的 “逻辑” 结构

整体结构:
tablespace(共享、独立) ---- segment(leaf / non-leaf / roolback) ----- extent(1M) ---- page(16K) ---- row

表空间

有 : 共享的 ibdata 与 独立的 .ibd 两种,独立的表空间中值记录数据、索引、插入缓冲bitmap页。 其他的 信息 如 undo 信息,插入缓冲索引页、事务系统信息、二次写缓冲 仍然是在 ibdata 中,所以会一直增大。并且前面提到了在 double write 的时候,也需要用到 共享表空间。

数据段 索引段 回滚段
数据为 B+树的叶子节点 索引段为非叶子节点

1MB 、 一次从磁盘申请多个区保证页的连续 64个页 16 KB
页有压缩的概念,但是区大小不变
刚开始新建的表会申请碎片的页空间(每个段开始前都有 32 个页的 碎片空间???),而不是连续的64个页的区。碎片页的来源是 其他 页的内部碎片空间吗 ????
32个碎片页空间使用完后, 表空间 开始以 一个区 一个区(1M) 的申请空间

默认 16 KB ,可以通过参数设置改变为 4K 8K
常见页有 : 数据页B-tree 、undo 页 、 system 页 、事务数据页 、 插入缓冲位图页 等等

每个页最多 7992 行, 16KB/2 - 200(最小的算,减去头部的一些值、标签等)

行记录的格式

compact

每一行的前面都有 几部分 的头部信息,然后才是 每一列的数据
变长字段长度列表 ----- NULL标志位 ----- 记录头信息(多种) ---- 列1 ---- 列2 ---------------------------------…

redundant

字段长度偏移列表 ---- 记录头继续你学 ---- 列1 ----- 列2 —

compressed && dynamic

主流形式 : 一行数据里面 : 各种头部信息 + 指向 真实数据页 的偏移量指针(20字节,off page) + 可能尾部信息
compressed 的可以对大类型数据进行压缩
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
---------- 测试结果并没有连接到 off page ???

char

针对 英文 数字的 char 类型,一个字符为 一个字节 ,CHAR 类型为定长的,但是 针对一个字符 多个字节的字符集 ,CHAR 类型被处理为 变长的 。比如汉字。

行溢出问题

varchar 65535 字节,一个页最多 16384 字节, 所以要创建 varchar (65535) 的字段,其实最多 65532 字段,varchar 说的65535 是所有varchar类型的总长度。
但是一个页最多16384字节,如果创建了 int(8),varchar(65520),varchar(10) 是可以的,但是针对溢出的部分,有特殊的机制。
所以这种情况下,针对每一行,.ibd 文件中,正常的部分之外,一行还会存放 768 bytes 的varchar 前缀数据 + 偏移量(偏移到 真正存放数据的 uncompressed BLOB Page )
即相当于 每一行 : 其他数据 + 前缀数据(保存一部分) + 指向一个 BLOB page 的指针 + 其他
如果 一个 页中可以放下至少两条数据 , 那么 varchar 的就不会 单独存放到一个 BLOB page。

数据页结构

FileHeader 38 bytes ------- 页的头信息
page header 56 bytes ------- 数据页的状态信息
…行记录
Infimun + supremum records ------ Infimun 比任何主键值都小的值 supremum 比任何主键值都大的值
user records ---------- 行记录 每条记录之间 通过 链表 连接

free space --------空闲空间 链表形式 记录删除后,空间加入空闲链表中(结构体??)
page directory ---------- 用于 在 B+ 数查到具体的数据页后,装入内存中,然后对页 通过 page directory 进行二叉查找到具体 的 记录。(总的来说就是 B+树程度上 查找记录 针对的 页 ,,,, 而 在页 中查找记录 ,是链表的结构)
file tailer 8bytes ----------检测页的完整性 : checksum && 与头部某个值相同

在这里插入图片描述

约束

1 . 主键、唯一、外键、not null 、default强制
2 . 错误数据的约束 ,比如 not null ,固定格式等。
3 . enum: 值中 之一
4 . set :无重复
5 . check 参数
6 . 创建触发器,在数据操作之前先检查一遍,错误写入日志
7 . 外键约束 父子表 ,子表不能插入外键在父表中不存在的值

视图

1 . 虚表,不存储实际数据,由SQL语句得来,相当于一个已经过滤好的数据集
2 . 不需要关心基表的各种结构,直接从 视图 里面取数据
3 . 可以对视图实现行的控制,因为是一个数据结构的存在,不同于数据实表,需要数据库权限
4 . 一定程度的数据安全
5 . mysql 中不支持物化
6 . 可以屏蔽 原表 对 视图的影响

分区

对很大的表进行分区,可能会提高查询速度,但是针对类似于需要全文检索的,分区形式的IO开销很大。

分区类型

1 . range 范围类型
对数据的 某一字段(最好是主键,辅助字段建立索引之后离散读取会好很多) 进行范围的划分,不同范围划分到不同的分区里面,这样针对某些操作的 速度可以提高很多, 可以置对一个分区的数据进行操作。
2 . list 类型
分区里面将 离散 的值全部定义好,不在分区的是会失败。
3 . hash 类型
只需要制定分区数量 与 hash的表达式。分区自动按照表达式计算应该分到哪一个分区,可以 自定义 字段。
4 . key
也是hash 函数,但是使用内置函数
5 . columns 分区
上面的 针对的 值 ,必须是 Integer 类型,不是的话,需要转化。
直接使用 对应 的数据类型进行分区,不需要转化成 integer 类型。

子分区

允许 range 和 list 再进行 hash 或者 key 的分区

null 的分区

range 中 比任何都要小
list 中必须提前显示指定
hash 和 key 的都是返回 0

性能

对于 OLAP 应用,一次需要很多数据,分区可以很好地提高效率
但是 对于 OLTP 应用,如果 针对 非主键的字段进行 等值查询,那么需要遍历十个分区(每个分区都有B+数的 IO ),并且是离散的访问。即使针对其他字段建立索引了,但是辅助索引的字段仍然可能存在于多个分区里面,IO开销还是很大。

分区 与 表的数据交换

表需要有相同结构
非分区表的数据需要满足该分区的条件
.
.

第五章 索引与算法

B+树
全文索引
自适应哈希索引

数据结构

二分查找
BST 与 平衡二叉树

B+树操作

插入 删除的分裂

B+树索引

聚集索引

针对主键建立的B+树,叶子节点是存放该行记录的对应的 页 。
叶子节点的数据页通过双向链表进行链接。(逻辑上是连续的,物理上不是,通过链表的指针进行链接)
每个叶子节点只存在一个 数据页 。
针对主键的查询非常快

辅助索引(非聚集索引)

对非主键建立辅助索引:即对这些字段新建一个 B+ 树,叶子节点除了存放对应的 键值对,还存有一个 bookmark 书签。书签存放的是 ---- 可以查询到这个 辅助索引的 对应的主键索引值,是通过遍历建立起来的,所以可能有多个。然后可以根据 查询到的主键索引,在聚集索引的B+树上面定位到数据页。
所以整体结构上,辅助索引 在 聚集索引上方,但是没有联系。是独立的,不会影响到聚集索引。

B+树索引的分裂

管理 ?????????????????????????

1 . alter 创建、删除
2 . fast index creation 加S 锁
3 . Online schema change ,事务创建的是,让读写事务对表操作
4 . Online DDL

cardinality 值

创建B+树索引比较适用于 数据重复 很少的。cardinality 表示 索引字段不重复记录的预估值。所以在 cardinality / rows 的值越接近于 1 时,b+索引越有用
.
采样 计算 cardinality 值,值更新发生在 insert 和 update 中,但是按照一定的策略才会去更新一次:“1/ 16 的数据更改了” --或者 – 发生变化的次数大于一个阈值
每次默认从叶子节点采样8个数据页,该参数可以设置。

B+树索引的使用

联合索引

多个字段 建立联合索引,左右子节点的分配首先 从第一字段开始匹配,然后向后。
第一个子弹的排序是顺序的,但是后面的数据是相对乱序的。所以针对后面字段的单个索引查询使用这个不好。
对于同一个的 叶子节点下的数据,第一个字段后面的一个字段的相对顺序是顺序的,不需要排队。

覆盖索引

直接通过 辅助索引 获得查询结构,不需要通过 辅助索引的叶子节点再次做一次聚集索引查询。也没有聚集索引关于 数据页 的IO 操作。索引,辅助索引的开销会小很多。比如: 统计 有多少的行记录,直接使用 辅助索引进行统计即可。即称为 覆盖索引。
(我觉得 覆盖 索引是一个抽象的概念,针对的是 非聚集的辅助索引 而言的)

不使用索引

范围查找 或者 JOIN 连接

index hint

强制让优化器执行指定的 索引。

Multi-Range read 优化 MRR

1 . 使得数据方位较为顺序,比如辅助索引查询到 所有的 主键便签后,根据主键的顺序进行排序,这样使得数据的访问变得相对的顺序了。
2 . 相对顺序可以减少缓冲池中页的替换次数
3 . 一次性处理,一次性读取
4 . 针对 范围查询 && join ,现将 辅助索引值放在缓存中,按照 rowID(主键) 排序之后,再查询。
总的就是 : 关于使用辅助索引在查询的时候,会先通过 聚集索引的进行排序,然后,再去处理,减少了很多的数据页IO。访问顺序了很多,不再是离散(离散读取的时候,硬盘层级的磁道不断的来回)

index condition pushdown 优化 ICP

对于有where 操作的过滤,在数据提取的时候就过滤掉,而不是全部提取出来后,再进行条件的比较。
(问题: 这种优化是由优化器自动选择的,如果 过滤 条件里面存在 主键字段才会这样吗?因为主键就可以确定大致范围了,还是说:再全局扫描 或者 使用索引读取的时候,每一行匹配的时候 , 都去过滤一次 ????
.

哈希算法

hash表

基槽 + 链表

innodb 中的hash

我的理解 :
1 . 首先关于一个 页 ,可以根据 space_id offset 唯一确定一个key 值,用这个key值来确定hash表中的基槽位置。
K = space_id << 20 + spaceid + offset 取模运算
基槽数目 = 缓冲池最大页数 的 2 倍的 稍大一点的质数(取模运算 才 公平,减少碰撞,取模可以取到每一个小于质数的数)
2 . 哈希表只能用于 等值 查询。
3 . 哈希表 提升 速度的部分在于 , 页数据在之前已经被读取到了缓冲池里面,但是是已链表的形式存储的。如果查询的时候 确定了space_id ,那么可以根据这个值判断需要的页是否已经缓冲在了缓冲池里面,如果是,那么可以直接通过 hash 的O(1),直接定位到缓冲池链表的具体的页 。
4 . 但是一个等值连接,首先还是会去查询 B + 树,如果是针对主键的,则直接使用聚集索引,否则建立辅助索引会快很多。通过这一次的B+树索引操作,确定的就是 space_id 。 那么可以判断已经缓冲。减少了磁盘的读取。但是没有缓冲在缓冲池的页,仍然需要从磁盘读取。
5 . 所以速度的提升在于 :缓冲池页的读取,从遍历查询 变成了 直接定位页。速度提升。

自适应哈希

自动建立的,都只能用于等值查询

.

全文检索

全文的关键词查询

倒排索引

针对关键词建立一个表(辅助表 ),存储了关键词 以及 关键词出现地方的主键值和位置。类似于前面的辅助索引,刚开始都是进行了全表的扫描统计的。

innodb 的全文索引

将所有的词进行划分,有主键ID,以及定位的位置。都在一个辅助表里面。
FTS index Cache 全文检索索引缓存,用于提高全文检索性能(红黑树),在缓存中,不需要每次都去访问辅助表。
??????没看懂

全文检索

全文检索,加入了某些规则。 match 函数 match() … against (),against 表示使用什么模式
三种模式 : natural language 、 Boolean 、 query expansion
.
.

第六章 锁

加锁操作 都是 在 读取数据页到缓冲池了,然后对数据进行的操作。

lock 与 latch

1 . lock : 针对事务、整个事务过程加锁(隔离级别)、行锁/表锁/意向锁、wait-for graph/ time out处理死锁
2 . latch : 针对线程、临界资源加锁、读写锁/互斥量 / 无死锁检测与处理。 ---- 底层关于操作系统的。
.

Lock

S lock 与 X lock 行锁
S 与 S 可以兼容
意向锁 : 行级别的 X S 锁,之前需要对 页 、 表 、 数据库 粗粒度级别加上 意向锁,innodb 只存在 表级别的 意向锁 (IX , IS)
IS 与 IX 可以兼容 、 IS 与 S 、S 与 S 可以兼容。其他都会阻塞。

一致性非锁定读

MVCC ,如果行已经加了X锁,那么read通过undo(逻辑操作日志)回滚得到之前版本的值。

一致性锁定读

需要显示的使,读取数据逻辑顺序一致的值,即等待X锁的完成。

自增长

现在 : 互斥量的自增长机制 ----- 之前 :对表进行加锁,再自增立即释放表锁。不等待事务完成。
自增的列必须是索引(其实 就是 主键)。

外键与锁

子表的数据插入操作时,需要对父表加S锁,一致性锁定读。保证数据约束的一致性。
.

锁的算法

行锁的三种算法(????????????????????????)

record lock:锁定记录本身(锁的是 主键 字段)
gap lock : 锁定一个范围,不锁记录本身
next-key lock : 锁定一个范围,并且锁本身,可以解决 幻读 问题。

锁的问题

幻读???????????????

脏读

脏数据 != 脏页
读脏页 很正常
!!!不是指读取脏页数据,而是 读取到了 其他事务修改了,但是还没有提交的数据,就是说其他事务可能最后失败,但是却读到了这个数据。(?????????等事务看完再理解)

不可重复读

一个事务读取同一个数据多次 , 期间有其他事务修改数据,并且提交了事务,完成修改。导致之前的事务前后读取的内容不一致。
.
关于 隔离级别 与 几种锁的问题的总结 :
.
Read uncommited 里面,一个事务可能读到另外一个事务没有 commit 的数据,导致脏读问题。
Read commited 里面,解决了脏读问题,但是存在一个事务对某个记录的多次读取,可能 其他事务在此期间 修改 并commit,导致 不可重复读(就是幻读 ,可能update insert delete) 的 问题,前后数据读取不一致。
Repeatable Read 里面,解决 不可重复读的问题。
Serializable 里面,串行化,隔离级别最高,最安全。
.
!!!幻读 == 不可重复读 !!!
幻读 问题,也是在 read commit 里面的问题,幻读是指,在一次事务里面的范围读取,会读到上一次读取没有读到的数据,即数据多了(少了 或者 修改了),这里也是有其他事务参与进来,并且修改还commit了 。
.
幻读(不可重复读)的解决 : 使用 next-key 的加锁方式,即在 加 record lock 的基础上,还要加 gap lock ,相当于在范围查询的时候,不止锁行记录,还要锁行记录后面所有满足条件的所有的行 ,锁 [ R1 , ∞ )。

丢失更新

即对同一个数据的,有两个线程读取了。然后两个线程先后用之前的值去更改。这样就会出现更新丢失的情况。
所以针对这种情况 : 用事务操作的时候,就加一个X锁,阻塞其他事务的访问,直到 修改提交之后。事务才释放X锁,另外一个事务才可以进行读取的操作。这种操作必须是 序列化 顺序的操作!!!
典型例子 : 读了两次分别给了两个用户,两个用户分别改。
.

阻塞

获取锁的阻塞
.

死锁

互相持有对方需要的锁的等待现象。
1 . 超时机制 : 等待时间超过 阈值,事务取消,并且通过undo的逻辑日志,对已经的操作进行回滚。但是直接回滚超时的事务可能 undo 量很大的,得不偿失。 这种情况下,阻塞也有可能被回滚。 这里的FIFO选择被回滚事务 是 什么样的机制??? 不太明白,既然不是主动去检测的,那又怎么维护一个 FIFO 的等待队列呢,如果等待就加入队列了,万一是死锁了,然后其他的事务被阻塞,是不是说其他被阻塞的事务也会作为回滚对象的选择,问题更大。还不如简单的直接回滚超时事务。
2 . wait-for graph :
锁的信息列表 : 每一个加了行锁的列 都有一个链表 : 链表信息是 事务ID,与 加锁类型
事务等待列表 : 事务发生等待后,会加入等待队列。包含等待的是哪一个行的锁。
根据上面的信息,对正在等待锁的事务建立一个图。
比如 :A 事务 等待锁 L1,L1被B事务占着,B事务等待锁 L2,锁L2被C事务占着,C事务等待锁L3,L3被A事务占着。
就形成了回路,是一种主动监测 死锁的方法。--------- 每一次有事务进入等待都会去检查一遍,发现了则回滚undo事务量最小的。

两种死锁 :
1 . AB-BA
2 . 一个事务A持有了当前记录 R1 的X锁,并且进行了插入 R2 操作,还获取了待插入记录 R2 的X锁,而同时 事务 B 在等待 R1 的S锁,这种情况也被主动识别为 死锁 。回滚undo量大的事务。(不懂为什么? 书上说,避免S锁的事务B继续向后查询会去获取 R2 ,但是获取R2不是应该的吗? 因为读取页行的操作时候,不就是一行一行读的,然后无法获取锁则阻塞?? 还是说,会先把不会阻塞的读了???最后才进行阻塞的????但是这样不太可能的吧
.

锁升级

就是一个事务的SQL如果需要涉及到的行锁太多了,然后事务页很多,来回的不断的阻塞情况发生,这样的话,可以将行锁升级为 页 锁或者表锁,直接让一个表里面只允许一个事务进入,大大的提高了效率。 但是问题是 : 这样并发效果很不好。
锁升级的情况 :
1 . 一个SQL 持有的锁太多了,超过了阈值,针对当前的事务进行锁升级
2 . 锁资源占用了太多的内存
锁升级 就想到了 锁降级,锁降级是Java开发里面的一个并发概念,针对可重入锁的概念,即当前的线程持有了写锁,但是这个线程希望写完之后同时获得对资源的读的能力,避免其他线程抢到读锁,那么在释放写锁之前,先加一个读锁,这里需要可重入。这就是锁降级,是不同的领域的概念
.
.

第七章 事务

ACID
原子性:一次事务要么成功,要么失败
一致性: 事务结束之后,数据库的完整性约束没有破坏,比如 外键、not null,unique
隔离性:事务之间在提交之前相互不可见,用锁实现。有四种隔离级别,分别解决几种问题。
持续性:提交就是永久了,保存在文件里面了。
.

事务的分类

扁平事务、带有保存点的扁平事务、链事务、嵌套事务、分布式事务
扁平的 : 失败全部重来,如果过程太多,undo 已经 重来开销太大。
有保存点的扁平 : 隔一部分保存一个状态,错误回滚到保存点,然后继续。但是感觉有点违背原子性???
分布式: 需要保证所有的事务都满足ACID,原子性,不然问题很大
.

事务的实现

隔离性 通过 锁 来实现。
原子性、一致性、持久性 通过 redo log && undo log 来实现。
redo 重做日志保证 原子性 和 持久性,记录的是页的物理修改操作
undo 保证一致性,记录的是 对行 的逻辑操作

redo

1 . redo log buffer (文件系统的缓冲空间,而不是缓冲池),redo log file
2 . redo buffer 写入磁盘,需要调用 fsync 操作,这个频率是有策略的
3 . 重做日志都是以块 512 bytes 进行划分的,所以每次写入磁盘的时候,是原子性的,不需要 double write ,不存在部分写失效的损坏问题。
4 . 每个重做日志块,有头部与尾部开销,实际 492 字节日志数据。
5 . 从文件系统缓存中刷新 log 块到磁盘。
6 . log group 即多个相同大小的重做日志文件组成,且每个file里面头部都会预留一部分空间,第一个文件该空间存放头部及其他信息。

redo日志格式

(一个先于实际物理页操作的 理论 的预想的 物理页操作记录)
redo_log_type 日志类型 … |… space 表空间ID … |… page_no 页偏移量 … | … redo log body 具体内容
.
LSN 日志序列号
8字节 不仅记录在 重做日志中,而且记录在每个页的header 里面(表示该页最后刷新的时候,当时LSN 的大小)。
1 . 表示重做日志写入的总字节数。
2 . checkpoint 的位置 , 小于checkpoint 的说明已经全部刷新到磁盘 , 都不需要重做。
3 . 页的版本
如果页的 LSN 版本小于 redo日志文件的LSN ,那么需要重做。重做的原因是,之前已经写入了重做日志文件,并且事务已经提交了,但是在还没有刷新脏页到磁盘之前宕机了。
如果等于,说明就是最新的,不需要重做。
.
-------- 一个大问题 : 事务提交之前,首先写 redo 日志文件,如果没有提交成功,事务操作会失败,那么已经写入的重做日志文件怎么处理? 然后如果事务成功提交之后,但是宕机了,那么脏页没有来得及刷新到磁盘。但是应有的操作已经刷新到了redo日志,宕机了可以通过这个来进行恢复。!!!但是,在前面提到了 checkpoint 技术,即检查点之前的数据是不需要 重做的,因为已经刷新到了磁盘,是不是说 我每一次刷新了 脏页到 磁盘的时候,都会有checkpoint的改变,还是说checkpoint 会累积一定的脏页刷新了,才进行改变。总的来说,redo日志就是用来保证,宕机的时候,在缓冲池里面的脏页,可以通过 redo 修改到最新的正确状态 ------&&------- redo 记录的是理论的物理页(这里我的理解是对缓冲池中脏页物理操作,不可能是磁盘,因为redo先于所有,在脏页刷新之前)操作,并不是实际发生在磁盘操作时的,因为事务是先写redo,宕机了再根据这个预先的理论物理页操作,进行实际的物理页操作。
---------顺便说一下前面说的,部分写失效无法通过redo恢复,因为 部分 写失效使得物理页损坏,那么记录的 理论物理页操作 对 错误原数据的修改 仍然是错误的,所以不能这样,只能 double write .

.

undo

1 . 共享表空间的 undo segment , undo log,存在共享表空间里面
2 . undo 是逻辑日志,并不是直接恢复到之前的状态,而是 对于每一个 SQL ,执行一个相反的操作。可以 调用 rollback 回滚。
3 . MVCC
4 . 写undo的过程同意需要写入 redo,因为一个对页的操作
5 . insert 和 update(delete update) 两种类型,undo 页是否回收删除根据 purge 确定,因为 存在MVCC。
.

group commit

1 . 事务提交 : 首先会写日志到重做日志缓冲,然后 再将重做日志缓冲写到(调用 fsync ) 重做日志文件里面,完成事务提交。等待多个事务的重做日志缓冲,一次调用 fsync 。
2 . 但是在开启 binlog 之后,二进制文件 的写入顺序必须和事务提交的顺序一致,因为在线的OLTP 应用都是主从数据库的。但是事务提交的时候,首先 写入 二进制日志,然后才开始 重做日志,但是为了保证一致,需要加锁,即在 重做日志的两阶段只能序列化的进行。这样就只能每次重做日志都调用 fsync 。
3 . 解决上面的问题 :BLGC的方式

开启了 binlog 的事务提交的过程 :

  1. Flush 阶段 : 将每个日志的二进制文件写入内存。每个阶段维护一个 事务的队列。
  2. Sync , 二进制文件从内存刷新到磁盘, 多个事务可以 一起commit。
  3. commit 阶段, 调用存储引擎的 事务的提交,即写入重做日志缓冲 、以及重做日志缓冲 group commit 到磁盘

这里的事务提交过程跟一般的有区别 : 比如之前说的,关于正常的事务提交,直接先写重做日志缓冲。

事务的隔离级别

uncommit read
commit read
repeatable read
serializable 序列化

分布式事务

事务三阶段 : innodb prepare ---- binlog ----- redo log
通过 XA 事务保证主从数据一致
-----因为 binlog 是提前写入的,但是可能在进行redo 的写入时宕机了,但是binlog提交了就已经表示事务提交,并且 binlog 已经被从数据库获取到了,所以 在 prepare 阶段 ,记录一下事务的情况。如果在引擎的事务提交失败了,下一次的重启操作,独自完成引擎级别的 redo 提交。因为缓冲池里面的脏页已经产生了,但是 redo log 没有写入,脏页的理论修改也不会记录下来,会造成数据的丢失。其实跟单机的事务一样,首先第一步就是 redo 的东西,必须保证原子性,持久性–只是这里通过一个预处理进行。

不好的事务习惯

循环提交
自动提交
自动回滚

长事务

分解成小的事务进行
.
.

第八章 备份

冷备热备
主从服务器通过二进制文件同步
从服务器做一个快照,避免主数据库的误操作
优点:负载均衡、 备份、复制、高可用性/故障后转移主数据库
…其他略
.
.

第九章 性能调优

1 . 合适CPU
2 . 内存
缓冲数据 以及 缓冲池的大小。问题 : 电脑自带的缓冲 与 数据库的缓冲池的关系,数据库缓冲池是建立在 电脑缓冲之上的吗?我的理解不是,因为内存的缓冲是用于 hit miss 的,但是缓冲池的 数据页,会存在很多的 脏页数据。内存的缓冲只会存在从磁盘读取的数据。那么就有这样一种可能,脏页刷新回了磁盘,然后再次读取 对应页的时候,如果缓冲池与内存缓冲没有关系,按照内存缓冲的LRU 算法等,很有可能 内存缓冲的页 还在里面并且是之前的页 ,那么数据有问题了。 所以说 ?????? ----- 数据库的缓冲池 应该是不可以建立在 内存的缓冲之上的!????!!!而是应该 与内存缓冲平级的存在,即磁盘上面的缓冲就是 数据库的缓冲池 。 存在miss 和 hit,但是是根据 页 的space_id 来确定,所以之前必须有一个B+的索引查询,然后遍历链表 或者 hash表定位查询是否 hit 。
64位的系统存在数据对齐的因素
3 . 硬盘
机械 、 固态(随机访问读取能力)
4 . RAID : 有好几个级别。 磁盘冗余阵列,用于数据容错恢复、增加处理量与容量。
5 . 操作系统
Linux稳定
6 . 文件系统的影响
比如window的 NTFS
7 . 测试工具
sysbench : 查询数据库的负载情况
在这里插入图片描述
mysql-tpcc : 评价软硬件性能

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值