将数据划分为页,以页为作为交互的数据单元,基本大小为16KB。
InnoDB为了不同的⽬的⽽设计了 许多种不同类型的⻚,⽐如存放表空间头部信息的⻚,存放Insert Buffer信息的⻚,存放INODE信息的⻚,存放undo⽇志信息的⻚等等等等。
SELECT * FROM record_compact WHERE C1=3
最笨的办法:从Infimum记录(最⼩记录)开始,沿着链表⼀直往 后找,这种暴力查找当然是不可取的,为此InnoDB设计一种目录索引。制作过程如下:
步骤一:将所有的记录(最大最小记录, 不包括已标记删除的记录)分组
步骤二:将每组中的最后一条记录(也就是组内最大的记录那条)的头信息的n_owned属性表示为改组拥有的记录条数
步骤三:将每组的最后一条记录的地址偏移量(槽)单独拿出来放在靠近页尾部的地方即page_directory页目录。所以这个⻚⾯⽬录就是由槽组成的。
我们平时以记录为单元向表中插入数据,这些记录在磁盘上的存储方式称为行格式或者记录格式。目前设计了4中行格式:Compact、Redundant、 dynamic、Compressed
行格式的语法:
CREATE TABLE 表名 (列信息) ROW_FOMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称
------------------------------------------------------------------------------------
例如:->USE dabao;
->CREATE TABLE record_format(c1 VARCHAR(10),c2 VARCHAR(10),c3 CHAR(10),c4 VARCHAR(10) NOT NULL) CHARSET=ascii ROW_FORMAT=COMPACT
INSERT INTO record_format(c1,c2,c3,c4)VALUES('AAA','BBBB','CCCC','DDDD'),('EEE','DDDD','DADA'NULL)
然后我们就开始解开每个行格式下的存储方式的神秘面纱。
compact分为记录的额外信息和记录的真实数据。
(1)变长字段长度列表
MySQL支持一些变长的数据类型,变长字段占用的存储空间分为两部分:真正的数据内容和占用的字节数。
把真正的数据内容占据的字节长度放在记录的开头,从而形成一个变字长的字段长度列表,各字段占用的字节数按照列的顺序 逆序 存放。根据每条记录中的列数据中的字符串大小,来判断具体使用1字节还是2个字节存储真实数据,InnoDB有自己的一套规则。M:选用的字符集类型中一个字符所占用的大小;W:变长类型的最大存储字符数;L:实际存储的字节大小。
if W*M<255 则采用1个字符存储真正字符串占用的字节数
else if W*M>255 && L<127 则采用1个字符存储真正字符串占用的字节数else 则采用2个字符存储真正字符串占用的字节数
变长字段长度列表只存储值为非NULL,
(2)NULL值 列表
不让把所有的null值都存储到真实数据中,所以compact列格式把null的列集中管理,存储到null值列表中,处理过程:
1.统计表中允许存储null的列表,如果表中没有可以null的列,则null值列表也不存在。否则每个允许null值的列占用一个位,并且逆序排列,二进制值为1时,该列的值为null。否则不为空。
2.MySQL中规定所有的null列必须存储在整个字节的位中,位数不足则最高位补零。
(3)记录头信息
5个固定字节数。记录当前记录条数,当前堆位置,下一条记录位置等信息。
(1) User Record
(2)记录头信息的秘密
1.delete_mask 这个属性表示当前记录是否被删除 占用一个位 值为1则已经删除。需要注意的是,这里的删除记录,并不是立即从磁盘中清除,是因为如果每次记录删除立即磁盘清除的话,就需要将其他记录在磁盘上重新排序需要消耗性能,所以只是打个标记,将所有的删除记录组成一个垃圾链表,标记的垃圾链表的空间则变为可重用空间,新来的记录就会覆盖标记删除的记录。
!!!删除记录位为1时与该记录假如垃圾链表其实是两个阶段。跟事务的删除 *** 作有关。
2.min_rec_mask B+树的每层非叶子节点的最小记录都会添加该标记。索引的时候会用到。
3.n_owned
4.heap_no 表示当前记录在页中的位置,有趣的是InnoDB会在每页上默认添加最大最小伪纪录作为补充称一条完整记录。
图中可以看出 最⼩记录和最⼤记录的heap_no值分 别是0和1,也就是说它们的位置最靠前。
5.record_type 表示当前记录类型,0:普通记录;1:非叶子节点记录,索引 2:最小记录;3 最大记录
6.next_record 表示从当前记录的真实数据到下⼀条记录的真实数据的 地址偏移量.下⼀条记录指得并不是按照我们插⼊顺序的下⼀条记录, ⽽是按照 主键值 由⼩到⼤的顺序的下⼀条记录.
删除一个记录时
MySQL中除了存储c1,c2,c3,c4用户自定义列数据外,MySQL会自动为每条记录添加三个隐藏列:ROW_id(主键,唯一标识一条记录,6个字节)、 transaction_id( 事务ID 6个字节 )ROLL_id(回滚指针,7个字节)
对于第2条记录中c3和c4列的值都为NULL,它们被存储在了前边的NULL值列表处,在记录的真实数据处就不再冗余存储,从⽽节省存储空间。
最终的compact列格式:
如果想要c3列也变为可变字段长度,则将定长字符集改为可变字符集。
ALTER TABLE record_format MODIFY COLUMN c3 CHAR(10) CHARACTER SET = utf8
对于Compact和Reduntant⾏格式来说,如果 某⼀列中的数据⾮常多的话,在本记录的真实数据处只会存储该列的前768个字节的数据和20个字节存储指向这些⻚的地址,然后把剩下的数据存放到其他⻚中,这个过程也叫做⾏溢出,存储超出768字节的那些⻚⾯也被称为溢出⻚。
MySQL中规定⼀个⻚中⾄少存放两⾏记录,溢出条件:
(1)每个⻚除了存放我们的记录以外,也需要存储⼀些额外的信 息,乱七⼋糟的额外信息加起来需要136个字节的空间。
(2)每个记录需要的额外信息是27字节。
假设⼀个列中存储的数据字节数为n,那么发⽣⾏溢出现象时需要满
⾜这个式⼦:136 + 2×(27 + n) >16384(页大小16K) 求解这个式⼦得出的解是:n >8098。
MySQL版本是5.7,它的默认⾏格式就是Dynamic,这俩⾏格式和Compact⾏格式挺像,只不过在处理⾏溢出数据时有点⼉分歧,它们不会在记录的真实数据处存储字段真实数据的前768个字节,⽽是把所有的字节都存储到其他⻚⾯中,只在记录的真实数据处存储其他⻚⾯的地址。
1. ⻚是MySQL中磁盘和内存交互的基本单位,也是MySQL是管理存储空间的基本单位。
2. 指定和修改⾏格式的语法如下:
CREATE TABLE 表名 (列的信息) ROW_FORMAT=⾏格式名称
ALTER TABLE 表名 ROW_FORMAT=⾏格式名称
3. InnoDB⽬前定义了4种⾏格式
* COMPACT⾏格式
*REDUNDANT
*DYNAMIC
* COMPRESSED
1. InnoDB为了不同的⽬的⽽设计了不同类型的⻚,我们把⽤于存放记录的⻚叫做数据⻚。
2. ⼀个数据⻚可以被⼤致划分为7个部分,分别是
File Header,表示⻚的⼀些通⽤信息,占固定的38字节。
Page Header,表示数据⻚专有的⼀些信息,占固定的56个字节。
Infimum + Supremum,两个虚拟的伪记录,分别表示⻚中的最⼩和最⼤记录,占固定的26个字节。
User Records:真实存储我们插⼊的记录的部分,⼤⼩不固定。
Free Space:⻚中尚未使⽤的部分,⼤⼩不确定。
Page Directory:⻚中的某些记录相对位置,也就是各个槽在⻚⾯中的地址偏移量,⼤⼩不固定,插⼊的记录越多,这个部分占⽤的空间越多。
File Trailer:⽤于检验⻚是否完整的部分,占⽤固定的8个字节。
3. 每个记录的头信息中都有⼀个next_record属性,从⽽使⻚中的所有记录串联成⼀个单链表
4. InnoDB会为把⻚中的记录划分为若⼲个组,每个组的最后⼀个记录的地址偏移量作为⼀个槽,存放在Page Directory中,所以在⼀个⻚中根据主键查找记录是⾮常快的,分为两步:
通过⼆分法确定该记录所在的槽。
通过记录的next_record属性遍历该槽所在的组中的各个记录。
5. 每个数据⻚的File Header部分都有上⼀个和下⼀个⻚的编号,所以所有的数据⻚会组成⼀个双链表。
6. 为保证从内存中同步到磁盘的⻚的完整性,在⻚的⾸部和尾部都会存储⻚中数据的校验和和⻚⾯最后修改时对应的LSN值,如果⾸部和尾部的校验和和LSN值校验不成功的话,就说明同步过程出现了问题。
表空间(ibd文件),一个MySQL实例可以对应多个表空间,用于存储记录,索引等数据。
段,分为数据段、索引段、回滚段,innodb是索引组织表,数据段就是B+Tree的叶子节点,索引段为非叶子节点,段用来管理多个区。
区,表空间的单元结构,每个区的大小为1M,默认情况下,innodb存储引擎页大小为16K,即一个区中一共有64个连续的页。
页,是innodb存储引擎磁盘管理的最小单元,每个页的大小为16K,为了保证页的连续性,innodb存储引擎每次从磁盘申请4~5个区。
行,innodb存储引擎数据是按行进行存储的。Trx_id 最后一次事务 *** 作的id、roll_pointer滚动指针。
i nnodb的内存结构 ,由Buffer Pool、Change Buffer和Log Buffer组成。
Buffer Pool : 缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常 *** 作的真实数据,在执行增删改查 *** 作时,先 *** 作缓冲池中的数据(若缓冲池么有数据,则从磁盘加载并缓存),然后再以一定频率刷新磁盘,从而减少磁盘IO,加快处理速度。
缓冲池以page页为单位,底层采用链表数据结构管理page,根据状态,将page分为三种类型:
1、free page 即空闲page,未被使用。
2、clean page 被使用page,数据没有被修改过。
3、dirty page 脏页,被使用page,数据被修改过,这个page当中的数据和磁盘当中的数据 不一致。说得简单点就是缓冲池中的数据改了,磁盘中的没改,因为还没刷写到磁盘。
Change Buffer :更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据page没有在Buffer Pool中,不会直接 *** 作磁盘,而会将数据变更存在更改缓冲区Change Buffer中,在未来数据被读取时。再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。
二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引页,同样,删除和更新可能会影响索引树中不相邻的二级索引页。如果每一次都 *** 作磁盘,会造成大量磁盘IO,有了Change Buffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。
Adaptive Hash Index: 自适应hash索引,用于优化对Buffer Pool数据的查询,InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。无需人工干预,系统根据情况自动完成。
参数:innodb_adaptive_hash_index
Log Buffer: 日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log、undo log),默认大小为16M,日志缓冲区的日志会定期刷新到磁盘中,如果需要更新,插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘IO。
参数: innodb_log_buffer_size 缓冲区大小
innodb_flush_log_at_trx_commit 日志刷新到磁盘时机
innodb_flush_log_at_trx_commit=1 表示日志在每次事务提交时写入并刷新到磁盘
2 表示日志在每次事务提交后写入,并每秒刷新到磁盘一次
0 表示每秒将日志写入并刷新到磁盘一次。
InnoDB 的磁盘结构,由系统表空间(ibdata1),独立表空间(*.ibd),通用表空间,撤销表空间(undo tablespaces), 临时表空间(Temporary Tablespaces), 双写缓冲区(Doublewrite Buffer files), 重做日志(Redo Log).
系统表空间(ibdata1): 系统表空间是更改缓冲区的存储区域,如果表是在系统表空间而不是每个表文件或者通用表空间中创建的,它也可能包含表和索引数据。
参数为: innodb_data_file_path
独立表空间(*.ibd): 每个表的文件表空间包含单个innodb表的数据和索引,并存储在文件系 统上的单个数据文件中。 参数: innodb_file_per_table
通用表空间: 需要通过create tablespace 语法创建,创建表时 可以指定该表空间。
create tablespace xxx add datafile 'file_name' engine=engine_name
create table table_name .... tablespace xxx
撤销表空间(undo tablespaces): MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16K,undo_001,undo_002),用于存储undo log 日志
临时表空间(Temporary Tablespaces): innodb使用会话临时表空和全局表空间,存储用 户创建的临时表等数据。
双写缓冲区(Doublewrite Buffer files): innodb引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入缓冲区文件中,便于系统异常时恢复数据。
重做日志(Redo Log): 是用来实现事务的持久性,该日志文件由两部分组成,重做日志缓冲区(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中,当事务提交之后会把修改信息都会存储到该日志中,用于在刷新脏页到磁盘时,发送错误时,进行数据恢复使用。以循环方式写入重做日志文件,涉及两个文件ib_logfile0,ib_logfile1。
那内存结构中的数据是如何刷新到磁盘中的? 在MySQL中有4个线程负责刷新日志到磁盘。
1、Master Thread, mysql核心后台线程,负责调度其它线程,还负责将缓冲池中的数据异 步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新,合并插入缓冲、undo页的回 收。
2、IO Thread,在innodb存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数 据库的性能,而IO Thead主要负责这些IO请求的回调。
4个读线程 Read thread负责读 *** 作
4个写线程write thread负责写 *** 作
1个Log thread线程 负责将日志缓冲区刷新到磁盘
1个insert buffer线程 负责将写入缓冲区内容刷新到磁盘
3、Purge Thread,主要用于回收事务已经提交了的undo log,在事务提交之后,undo log 可能不用了,就用它来回收。
4、Page Cleaner Thread, 协助Master Thread 刷新脏页到磁盘的线程,它可以减轻主线程 的压力,减少阻塞。
事务就是一组 *** 作的集合,它是一个不可分割的工作单位,事务会把所有的 *** 作作为一个整体一起向系统提交或撤销 *** 作请求,即这些 *** 作要么同时成功,要么同时失效。
事务的4大特性分为:
如何保证事务的4大特性,原子性,一致性和持久性是由innodb存储引擎底层的两份日志来保证的,分别是redo log和undo log。对于隔离性是由锁机制和MVCC(多版本并发控制)来实现的。
redo log,称为重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。该日志文件由两部分组成: 重做日志缓冲redo log buffer及重做日志文件redo log file,前者是在内存中,后者是在磁盘中,当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发送错误时,进行数据的恢复使用,从而保证事务的持久性。
具体的 *** 作流程是:
1、客户端发起事务 *** 作,包含多条DML语句。首先去innodb中的buffer pool中的数据页去查找有没有我们要更新的这些数据,如果没有则通过后台线程从磁盘中加载到buffer pool对应的数据页中,然后就可以在缓冲池中进行数据 *** 作了。
2、此时缓冲池中的数据页发生了变更,还没刷写到磁盘,这个数据页称为脏页。脏页不是实时刷新到磁盘的,而是根据你配置的刷写策略进行刷写到磁盘的(innodb_flush_log_at_trx_commit,0,1,2三个值)。如果脏页在往磁盘刷新的时候出现了故障,会丢失数据,导致事务的持久性得不到保证。为了避免这种现象,当对缓冲池中的数据进行增删改 *** 作时,会把增删改记录到redo log buffer当中,redo log buffer会把数据页的物理变更持久化到磁盘文件中(ib_logfile0/ib_logfile1)。如果脏页刷新失败,就可以通过这两个日志文件进行恢复。
undo log,它是用来解决事务的原子性的,也称为回滚日志。用于记录数据被修改前的信息,作用包括:提供回滚和MVCC多版本并发控制。
undo log和redo log的记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,当update一条记录时,它记录一条对应相反的update记录,当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
undo log销毁: undo log 在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日子可能用于MVCC。
undo log存储: undo log 采用段的方式进行管理和记录,存放在前面介绍的rollback segment回滚段中,内部包含1024个undo log segment。
mvcc(multi-Version Concurrency Control),多版本并发控制,指维护一个数据的多个版本,使得读写 *** 作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能,MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段,undo log日志、readView。
read committed 每次select 都生成一个快照读
repeatable read 开启事务后第一个select语句才是快照读的地方
serializable 快照读会退化为当前读。
mvcc的实现原理
DB_TRX_ID: 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID
DB_ROLL_PTR: 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个 版本
DB_ROW_ID: 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。
m_ids当前活跃的事务ID集合
min_trx_id: 最小活跃事务id
max_trx_id: 预分配事务ID,当前最大事务id+1,因为事务id是自增的
creator_trx_id: ReadView创建者的事务ID
版本链数据访问规则:
trx_id: 表示当前的事务ID
1、trx_id == creator_trx_id? 可以访问读版本-->成立的话,说明数据是当前这个事务更改的
2、trx_id 成立,说明数据已经提交了。
3、trx_id>max_trx_id?不可用访问读版本->成立的话,说明该事务是在ReadView生成后才开启的。
4、min_trx_id
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)