世界上最快的捷径,就是脚踏实地

重要,知识点:InnoDB的插入缓冲

阅读(543)

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

InnoDB引擎有几个重点特性,为其带来了更好的性能和可靠性:

  • 插入缓冲(Insert Buffer)
  • 两次写(Double Write)
  • 自适应哈希索引(Adaptive Hash Index)
  • 异步IO(Async IO)
  • 刷新邻接页(Flush Neighbor Page)

今天我们的主题就是 插入缓冲(Insert Buffer),由于InnoDB引擎底层数据存储结构式B+树,而对于索引我们又有聚集索引和非聚集索引。

在进行数据插入时必然会引起索引的变化,聚集索引不必说,一般都是递增有序的。而非聚集索引就不一定是什么数据了,其离散性导致了在插入时结构的不断变化,从而导致插入性能降低。

所以为了解决非聚集索引插入性能的问题,InnoDB引擎 创造了Insert Buffer。

Insert Buffer 的存储

内存池

看到上图,可能大家会认为Insert Buffer 就是InnoDB 缓冲池的一个组成部分。

重点:其实对也不对,InnoDB 缓冲池确实包含了Insert Buffer的信息,但Insert Buffer 其实和数据页一样,也是物理存在的(以B+树的形式存在共享表空间中)。

Insert Buffer 的作用

先说几个点:

  • 一张表只能有一个主键索引,那是因为其物理存储是一个B+树。(别忘了聚集索引叶子节点存储的数据,而数据只有一份)

  • 非聚集索引叶子节点存的是聚集索引的主键

聚集索引的插入

首先我们知道在InnoDB存储引擎中,主键是行唯一的标识符(也就是我们常叨叨的聚集索引)。我们平时插入数据一般都是按照主键递增插入,因此聚集索引都是顺序的,不需要磁盘的随机读取。

比如表:

CREATE TABLE test(
    id INT AUTO_INCREMENT,
    name VARCHAR(30),
    PRIMARY KEY(id)
);

如上我创建了一个主键 id,它有以下的特性:

  • Id列是自增长的
  • Id列插入NULL值时,由于AUTO_INCREMENT的原因,其值会递增
  • 同时数据页中的行记录按id的值进行顺序存放

一般情况下由于聚集索引的有序性,不需要随机读取页中的数据,因为此类的顺序插入速度是非常快的。

但如果你把列 Id 插入UUID这种数据,那你插入就是和非聚集索引一样都是随机的了。会导致你的B+ tree结构不停地变化,那性能必然会受到影响。

非聚集索引的插入

很多时候我们的表还会有很多非聚集索引,比如我按照b字段查询,且b字段不是唯一的。如下表:

CREATE TABLE test(
    id INT AUTO_INCREMENT,
    name VARCHAR(30),
    PRIMARY KEY(id),
    KEY(name)
);

这里我创建了一个x表,它有以下特点:

  • 有一个聚集索引 id
  • 有一个不唯一的非聚集索引 name
  • 在插入数据时数据页是按照主键id进行顺序存放
  • 辅助索引 name的数据插入不是顺序的

非聚集索引也是一颗B+树,只是叶子节点存的是聚集索引的主键和name 的值。

因为不能保证name列的数据是顺序的,所以非聚集索引这棵树的插入必然也不是顺序的了。

当然如果name列插入的是时间类型数据,那其非聚集索引的插入也是顺序的。

Insert Buffer 的到来

可以看出非聚集索引插入的离散性导致了插入性能的下降,因此InnoDB引擎设计了 Insert Buffer来提高插入性能 。

我来看看使用Insert Buffer 是怎么插入的:

索引页使用

首先对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中。

若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中。

给外部的感觉好像是树已经插入非聚集的索引的叶子节点,而其实是存放在其他位置了

以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,通常会将多个插入操作一起进行merge,这就大大的提升了非聚集索引的插入性能。

Insert Buffer的使用要求

  • 索引是非聚集索引
  • 索引不是唯一(unique)的

只有满足上面两个必要条件时,InnoDB存储引擎才会使用Insert Buffer来提高插入性能。

那为什么必须满足上面两个条件呢?

第一点索引是非聚集索引就不用说了,人家聚集索引本来就是顺序的也不需要你

第二点必须不是唯一(unique)的,因为在写入Insert Buffer时,数据库并不会去判断插入记录的唯一性。如果再去查找肯定又是离散读取的情况了,这样InsertBuffer就失去了意义。

Insert Buffer信息查看

我们可以使用命令SHOW ENGINE INNODB STATUS来查看Insert Buffer的信息:

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 7545, free list len 3790, seg size 11336, 
8075308 inserts,7540969 merged sec, 2246304 merges
...

使用命令后,我们会看到很多信息,这里我们只看下INSERT BUFFER 的:

  • seg size 代表当前Insert Buffer的大小 11336*16KB

  • free listlen 代表了空闲列表的长度

  • size 代表了已经合并记录页的数量

  • Inserts 代表了插入的记录数

  • merged recs 代表了合并的插入记录数量

  • merges 代表合并的次数,也就是实际读取页的次数

merges:merged recs大约为1∶3,代表了Insert Buffer 将对于非聚集索引页的离散IO逻辑请求大约降低了2/3

Insert Buffer的问题

说了这么多针对于Insert Buffer的好处,但目前Insert Buffer也存在一个问题:

即在写密集的情况下,插入缓冲会占用过多的缓冲池内存(innodb_buffer_pool),默认最大可以占用到1/2的缓冲池内存。

占用了过大的缓冲池必然会对其他缓冲池操作带来影响

Insert Buffer的优化

MySQL5.5之前的版本中其实都叫做Insert Buffer,之后优化为 Change Buffer可以看做是 Insert Buffer 的升级版。

插入缓冲( Insert Buffer)这个其实只针对 INSERT 操作做了缓冲,而Change Buffer 对INSERT、DELETE、UPDATE都进行了缓冲,所以可以统称为写缓冲,其可以分为:

  • Insert Buffer

  • Delete Buffer

  • Purgebuffer

总结:

Insert Buffer到底是个什么?

  • 其实Insert Buffer的数据结构就是一棵B+树。

  • 在MySQL 4.1之前的版本中每张表有一棵Insert Buffer B+树

  • 目前版本是全局只有一棵Insert Buffer B+树,负责对所有的表的辅助索引进行Insert Buffer

  • 这棵B+树存放在共享表空间ibdata1中

以下几种情况下 Insert Buffer会写入真正非聚集索引,也就是所说的Merge Insert Buffer

  • 当辅助索引页被读取到缓冲池中时
  • Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间时
  • Master Thread线程中每秒或每10秒会进行一次Merge Insert Buffer的操作

一句话概括下:

Insert Buffer 就是用于提升非聚集索引页的插入性能的,其数据结构类似于数据页的一个B+树,物理存储在共享表空间ibdata1中 。

每日一个知识点:分布式系统的一些理念

阅读(678)

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解。

此处不涉及过多的原理性解读,只作为一种抛砖引玉。

真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长。

分布式系统出现的目的

一条定理,分布式系统的首要目标就是为了提升系统的整体性能和吞吐量

如果我们设计出来的分布式系统占用了10多台机器,其性能才勉强提升了2倍,那你说你图啥呢?

当然,为了使我们的分布式架构性能达到最优,我们仍需要尽可能的提示单机程序性能。掌握更多的高性能程序设计和编程技巧,比如多线程并发编程、多进程高性能IPC通信、高性能的网络框架等。

分布式系统出现的问题

虽然分布式系统会大大增加了我们分布式系统的性能和吞吐,但其存在着让人无法回避的风险和问题,就是系统的复杂性导致发生故障的概率大大增加了

小到一个硬盘故障,大到服务集群整体挂掉。分布式系统下故障概率的增加,除了受到网络通信天生的不可靠性及物理上分布部署的影响,还受到X86服务器品质等的影响。

分布式系统的关键点

分布式系统设计有两大关键点:

  • 性能
  • 容错性

举个例子:

我们设计一个分布式存储系统,出于对性能的考虑,在写文件时要先将数据写到某台机器上并立即返回,随后异步的进行数据备份到其他节点,这种设计虽然性能最好,但存在”容错性”的风险。

如果此时文件刚写完,目标机器发送故障,那就有可能会导致文件丢失。但如果同时写多个机器成功后再返回,又会导致性能下降,因为该耗时取决最慢的那台机器的性能。

其中由于性能指标是绝对的,而容错性指标是相对的,在实际场景中对于不同的数据和业务需求,我们对容错性可以存在很大的不同。

  • 比如允许丢失一些的日志类数据

  • 比如允许一些数据暂时不一致但最终达到一致

  • 比如对交易类的数据要求高可靠性

所以根据不同的业务需求,在分布式系统设计时都会提供多种的容错性策略,这是我们在学习和设计分布式系统时也需要关注的一点。

分布式系统的设计思路

分布式系统设计中有两大思路:

  • 中心化
  • 去中心化

中心化

在分布式架构设计里,中心化始终是一个主流设计

设计思想:

  • 分布式集群中的节点可分为两种角色:Leader和Worker

  • Leader负责分发任务并监督Worker

  • 如果Leader发现某个Worker因意外状况不能正常执行任务,则将该Worker从Worker队列去除,并将其任务分给其他Worker

基于容器技术的微服务架构Kubernetes就恰好采用了这一设计思路

存在的问题:

  • Leader的安全问题,如果Leader出了问题,就有可能导致集群崩溃

  • Leader能力问题,如果系统设计和实现的不好,那么有可能瓶颈就会出现在Leader上

去中心化

去中心化设计里通常不区分Leader和Worker这两种角色,比如全球互联网就是一个典型去中心化的分布式系统,联网任意节点设备怠机,都只会影响很小的范围功能。

去中心化设计的核心:

  • 整个分布式系统中不存在一个区别于其他节点的Leader

  • 每个节点都需要与其他节点对话才能获取必要的集群信息

去中心化的难题:

  • 脑裂问题,这种情况发送概率很低,但影响很大

脑裂指一个集群由于网络的故障,被分为至少两个彼此无法通信的单独集群,此时如果两个集群各自工作,则可能会产生严重的数据冲突和错误。

一般的设计思路是,当集群判断发生了脑裂问题时,规模较小的集群就“自杀”或者拒绝服务。

实际上,完全意义的真正去中心化的分布式系统并不多见。

现在很多都是,在外部看来去中心化但工作机制采用了中心化设计思想。

在这种架构下,集群中的Leader是被动态选择出来的,而不是人为预先指定的,而且在集群发生故障的情况下,集群的成员会自发地选举新的Leader。

最典型的案例就是ZooKeeper及用Go实现的Etcd。

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

每日一个知识点:了解InnoDB的Checkpoint技术

阅读(425)

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解。

此处不涉及详细的原理性解读,只作为一种抛砖引玉。

真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长。

一句话概括,Checkpoint技术就是将缓存池中脏页在某个时间点刷回到磁盘的操作

遇到的问题 ?

图片名称

都知道缓冲池的出现就是为了解决CPU与磁盘速度之间的鸿沟,免得我们在读写数据库时还需要进行磁盘IO操作。有了缓冲池后,所有的页操作首先都是在缓冲池内完成的。

如一个DML语句,进行数据update或delete 操作时,此时改变了缓冲池页中的记录,此时因为缓冲池页的数据比磁盘的新,此时的页就叫做脏页。

不管怎样,总会后的内存页数据需要刷回到磁盘里,这里就涉及几个问题:

  • 若每次一个页发生变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的
  • 若热点数据集中在某几个页中,那么数据库的性能将变得非常差
  • 如果在从缓冲池将页的新版本刷新到磁盘时发生了宕机,那么数据就不能恢复了

Write Ahead Log(预写式日志)

WAL策略解决了刷新页数据到磁盘时发生宕机而导致数据丢失的问题,它是关系数据库系统中用于提供原子性和持久性(ACID 属性中的两个)的一系列技术。

WAL策略核心点就是

redo log,每当有事务提交时,先写入 redo log(重做日志),在修改缓冲池数据页,这样当发生掉电之类的情况时系统可以在重启后继续操作

WAL策略机制原理

InnoDB为了保证数据不丢失,维护了redo log。在缓冲池的数据页修改之前,需要先将修改的内容记录到redo log中,并保证redo log早于对应的数据页落盘,这就是WAL策略。

当故障发生而导致内存数据丢失后,InnoDB会在重启时,通过重放redo log,将缓冲池数据页恢复到崩溃前的状态。

Checkpoint

按理说有了WAL策略,我们就可以高枕无忧了。但其问题点又出现在redo log上面:

  • redo log 不可能是无限大的,不能没完没了的存储我们的数据等待一起刷新到磁盘
  • 在数据库怠机恢复时,如果redo log 太大的话恢复的代价也是非常大的

所以为了解决脏页的刷新性能,脏页应该在什么时间、什么情况下进行脏页的刷新就用到了Checkpoint技术。

Checkpoint 的目的

1、缩短数据库的恢复时间

当数据库怠机恢复时,不需要重做所有的日志信息。因为Checkpoint前的数据页已经刷回到磁盘了。只需要Checkpoint后的redo log进行恢复就好了。

2、缓冲池不够用时,将脏页刷新到磁盘

当缓冲池空间不足时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。

3、redo log不可用时,刷新脏页

图片名称

如图redo log 的不可用是因为当前数据库对其设计都是循环使用的,所以其空间并不是无限大。

当redo log被写满, 因为此时系统不能接受更新, 所有更新语句都会被堵住。

此时必须强制产生Checkpoint需要将 write pos 向前推进,推进范围内的脏页都需要刷新到磁盘

Checkpoint 的种类

Checkpoint发生的时间、条件及脏页的选择等都非常复杂。

Checkpoint 每次刷新多少脏页到磁盘?

Checkpoint每次从哪里取脏页?

Checkpoint 什么时间被触发?

面对上面的问题,InnoDB存储引擎内部为我们提供了两种Checkpoint:

  • Sharp Checkpoint

    发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式,参数innodb_fast_shutdown=1

  • Fuzzy Checkpoint

    InnoDB存储引擎内部使用这种模式,只刷新一部分脏页,而不是刷新所有的脏页回磁盘

FuzzyCheckpoint发生的情况

  • Master Thread Checkpoint

    差不多以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘。

    这个过程是异步的,即此时InnoDB存储引擎可以进行其他的操作,用户查询线程不会阻塞

  • FLUSH_LRU_LIST Checkpoint

    因为LRU列表要保证一定数量的空闲页可被使用,所以如果不够会从尾部移除页,如果移除的页有脏页,就会进行此Checkpoint。

    5.6版本后,这个Checkpoint放在了一个单独的Page Cleaner线程中进行,并且用户可以通过参数innodb_lru_scan_depth控制LRU列表中可用页的数量,该值默认为1024

  • Async/Sync Flush Checkpoint

    指的是redo log文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的

    5.6版本后不会阻塞用户查询

  • Dirty Page too much Checkpoint
    即脏页的数量太多,导致InnoDB存储引擎强制进行Checkpoint。

    其目的总的来说还是为了保证缓冲池中有足够可用的页。

    其可由参数innodb_max_dirty_pages_pct控制,比如该值为75,表示当缓冲池中脏页占据75%时,强制进行CheckPoint

总结

  • 因为CPU和磁盘间的鸿沟的问题,从而出现缓冲池数据页来加快数据库DML操作

  • 因为缓冲池数据页与磁盘数据一致性的问题,从而出现WAL策略(核心就是redo log)

  • 因为缓冲池脏页的刷新性能问题,从而出现Checkpoint技术

InnoDB 为了提高执行效率,并不会每次DML操作都和磁盘交互进行持久化。而是通过Write Ahead Log 先策略写入redo log保证事物的持久化。

对于事物中修改的缓冲池脏页,会通过异步的方式刷盘,而内存空闲页和redo log的可用是通过Checkpoint技术来保证的。

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

每日一个知识点:了解InnoDB的后台线程

阅读(520)

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

>

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解。

此处不涉及详细的原理性解读,只作为一种抛砖引玉。

真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长。

一、存储引擎架构

主要有两方面:

  • 后台线程
  • 内存

上图是InnoDB的存储引擎的体系架构,从图可见,InnoDB存储引擎有多个后台线程来对其进行内存池等操作的。

因为InnoDB存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。

二、后台线程

后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。

此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能恢复到正常运行状态

#### Master Thread

  • 一个非常核心的后台线程

  • 主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性

  • 包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、UNDO页的回收

IO Thread

  • 引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大提高数据库的性能。而IO Thread的工作主要是负责这些IO请求的回调(call back)处理

  • InnoDB 1.0版本之前共有4个IO Thread,分别是write、read、insert buffer和log IO thread

  • 从InnoDB 1.0.x版本开始,read thread和write thread分别增大到了4个

  • 查看版本 SHOW VARIABLES LIKE ‘innodb_version’

  • 查看线程数 SHOW VARIABLES LIKE ‘innodb_%io_threads’

  • 查看线程状态 SHOW ENGINE INNODB STATUS

Purge Thread

  • 用于清理的线程,配置参数 innodb_purge_threads=1

  • 事务被提交后,其所使用的undolog可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页

  • 1.1版本之前,purge操作仅在InnoDB存储引擎的Master Thread中完成

  • 1.1版本开始,purge操作可以独立到单独的线程中进行,以此来减轻MasterThread的工作

  • 1.1版本中,即使将innodb_purge_threads设为大于1,InnoDB存储引擎启动时也会将其设为1

  • 1.2版本开始,InnoDB支持多个Purge Thread,这样做的目的是为了进一步加快undo页的回收

Page Cleaner Thread

  • 在InnoDB 1.2.x版本中引入的

  • 其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成

  • 其目的是为了减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能

每日一个知识点:不同的存储引擎

阅读(470)

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解。

此处不涉及详细的原理性解读,只作为一种抛砖引玉。

真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长。

通过前几天的知识点我们知道,在Innodb体系架构里,主要包含后台线程和内存两大块,今天就来说下Innodb体系架构里内存的一些知识点。架构图如下:

Innodb的内存

老规矩,先上图,从全局看一下:

目前我们可以看出,InnoDB内存主要有两大部分:

  • 缓冲池
  • 重做日志缓冲

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。

因此可将其视为基于磁盘的数据库系统(Disk-base Database)。

在数据库系统中,由于CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能

缓冲池

缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。

缓冲池中缓存的数据页类型:

  • 索引页
  • 数据页
  • undo页
  • 插入缓冲(insert buffer)
  • 自适应哈希索引(adaptive hash index)
  • InnoDB存储的锁信息(lock info)
  • 数据字典信息(data dictionary

读数据操作

  • 首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中

  • 下一次再读相同的页时,首先判断该页是否在缓冲池中

  • 若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页

写数据操作

  • 首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上

  • 页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发

  • 通过一种称为Checkpoint的机制刷新回磁盘

配置参数:

  • innodb_buffer_pool_instances, 缓冲池实例个数默认1,InnoDB 1.0.x版本开始使用,每个页根据哈希值平均分配到不同缓冲池实例中。减少数据库内部的资源竞争,增加数据库的并发处理能力。
  • innodb_buffer_pool_size,缓冲池大小

命令参数:

  • 可通过命令查看缓冲池大小 SHOW VARIABLES LIKE ‘innodb_buffer_pool_size’
  • 通过命令可查看缓冲池实例个数 SHOW VARIABLES LIKE ‘innodb_buffer_pool_instances’

缓冲池的管理

缓冲池是一个很大的内存区域,其中存放各种类型的页。其实这么大的内存区域是通过LRU算法来进行管理的,而这里的LRU也是InnoDB 对传统的做了一些优化

LRU List

传统LRU:

  • 最频繁使用的页在LRU列表的前端
  • 最少使用的页在LRU列表的尾端
  • 缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页

InnoDB改进版LRU:

  • LRU列表中加入了midpoint位置
  • 新读取到的页是放入到LRU列表的midpoint位置
  • 默认配置下,该位置在LRU列表长度的5/8处。midpoint位置可由参数innodb_old_blocks_pct控制
  • 引入innodb_old_blocks_time 参数,用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端

Free List

  • LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何的页。这时页都存放在Free列表中.

  • 当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页

  • 当页从LRU列表的old部分加入到new部分时,称此时发生的操作为page made young,而因为innodb_old_blocks_time的设置而导致页没有从old部分移动到new部分的操作称为page not made young

  • 可以通过命令SHOW ENGINE INNODBSTATUS来观察LRU列表及Free列表的使用情况和运行状态

Flush List

  • LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新回磁盘,二者互不影响

  • 在LRU列表中的页被修改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。

  • 这时数据库会通过CHECKPOINT机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。

  • 脏页既存在于LRU列表中,也存在于Flush列表中。

缓冲池中的页还可能会被分配给自适应哈希索引、Lock信息、InsertBuffer等页,而这部分页不需要LRU算法进行维护,因此不存在于LRU列表中。

重做日志缓冲(redo log buffer)

  • InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件

  • 重做日志缓冲一般不需要设置得很大,因为一般情况下每一秒钟会将重做日志缓冲刷新到日志文件

  • 该值可由配置参数innodb_log_buffer_size控制,默认为8MB

  • 可通过命令查看大小 SHOW VARIABLES LIKE ‘innodb_log_buffer_size’

什么时候刷新到磁盘?

  • Master Thread每一秒将重做日志缓冲刷新到重做日志文件

  • 每个事务提交时会将重做日志缓冲刷新到重做日志文件

  • 当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件

每日一个知识点:数据库和实例

阅读(531)

每日一个知识点系列的目的是针对某一个知识点进行概括性总结。

可在一分钟内完成知识点的阅读理解,此处不涉及详细的原理性解读,只作为一种抛砖引玉。

真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长

数据库是由一个个文件组成(一般来说都是二进制的文件)的,要对这些文件执行诸如SELECT、INSERT、UPDATE和DELETE之类的数据库操作是不能通过简单的操作文件来更改数据库的内容,需要通过数据库实例来完成对数据库的操作

数据库:

  • 数据库是文件的集合,是依照某种数据模型组织起来并存放于二级存储器中的数据集合

  • 存储格式是以frm、MYD、MYI、ibd结尾的文件

实例:

  • 实例是程序,是位于用户与操作系统之间的一层数据管理软件,用户对数据库数据的任何操作

  • MySQL数据库由后台线程以及一个共享内存区组成

  • 共享内存可以被运行的后台线程所共享

  • 数据库实例才是真正用于操作数据库文件的

配置参数:

  • 按/etc/my.cnf→/etc/mysql/my.cnf→/usr/local/mysql/etc/my.cnf→~/.my.cnf的顺序读取配置文件的

  • 如果几个配置文件中都有同一个参数,以读取到的最后一个配置文件中的参数为准

  • Linux下配置文件一般放在/etc/my.cnf

  • 配置文件的后缀名可能是.cnf,也可能是.ini

每日一个知识点:关于磁盘的一些事儿

阅读(512)

爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方。

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解,此处不涉及详细的原理性解读,只作为一种抛砖引玉。

真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长。

1、扇区:

盘片被分为许多扇形的区域,每个区域叫一个扇区,硬盘中每个扇区的大小固定为512字节

2、磁道:

盘片表面上以盘片中心为圆心,不同半径的同心圆环称为磁道

3、一个I/O请求所花费的时间(寻道时间+旋转延迟+数据传输时间(约10ms)):

当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间

4、IOPS与吞吐量(连续读写性能很好,但随机读写性能很差):

机械硬盘的连续读写性能很好,但随机读写性能很差,这主要是因为磁头移动到正确的磁道上需要时间,随机读写时,磁头需要不停的移动,时间都浪费在了磁头寻址上,所以性能不高

5、常见磁盘的随机读写最大IOPS:

7200rpm的磁盘 IOPS = 76 IOPS

10000rpm的磁盘IOPS = 111 IOPS

15000rpm的磁盘IOPS = 166 IOPS

6、磁盘吞吐量:

指单位时间内可以成功传输的数据数量

磁盘阵列与服务器之间的数据通道对吞吐量影响很大

顺序读写频繁的应用,如视频点播,关注连续读写性能、数据吞吐量是关键衡量指标

7、磁盘的寻址模式

CHS寻址模式:

需要分别存储每个区域的柱面、磁头和扇区三个参数,使用时再分别读取三个参数,然后在送到磁盘控制器去执行。

CHS模式无法管理超过8064 MB的硬盘,因此工程师们发明了更加简便的LBA寻址方式

LBA寻址模式:

LBA编址方式将CHS这种三维寻址方式转变为一维的线性寻址,它把硬盘所有的物理扇区的C/H/S编号通过一定的规则转变为一线性的编号,系统效率得到大大提高,避免了烦琐的磁头/柱面/扇区的寻址方式

每日一个知识点:什么时候会触发Full GC

阅读(796)

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解。

此处不涉及详细的原理性解读,只作为一种抛砖引玉。

真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长。

世界上最快的捷径,就是脚踏实地。本文已收录架构技术专栏关注这个喜欢分享的地方,每日获得一个知识点

1. 调用 System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

2. 未指定老年代和新生代大小,堆伸缩时会产生fullgc,所以一定要配置-Xmx、-Xms

3. 老年代空间不足

老年代空间不足的常见场景比如大对象、大数组直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。

除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。

还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

在执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space

4. JDK 1.7 及以前的(永久代)空间满

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静

态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也

会执行 Full GC。

如果经过 Full GC 仍然回收不了,那么虚拟机会抛出java.lang.OutOfMemoryError PermGen space

为避免以上原因引起的 Full GC,可采用的方法为增大Perm Gen或转为使用 CMS GC。

5. 空间分配担保失败

空间担保,下面两种情况是空间担保失败:

1、每次晋升的对象的平均大小 > 老年代剩余空间

2、Minor GC后存活的对象超过了老年代剩余空间

注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当出现这两种状况的时候就有可能会触发Full GC。

promotion failed 是在进行 Minor GC时候,survivor space空间放不下只能晋升老年代,而此时老年代也空间不足时发生的。

concurrent mode failure 是在进行CMS GC过程,此时有对象要放入老年代而空间不足造成的,这种情况下会退化使用Serial Old收集器变成单线程的,此时是相当的慢的。

怎么调优

围绕一个点,策略就是尽量把对象在新生代使用回收,减少晋升老年代的几率

开源项目:

  • 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cubic

  • 摄像头视频流采集:https://gitee.com/sanjiankethree/cubic-video

  • Github 文章收录:https://github.com/qianglu1989/technology

每日一个知识点:性能杀手伪共享

阅读(614)

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解,此处不涉及详细的原理性解读,只作为一种抛砖引玉。真正的理解一定是你自我研究探索所收获的知识,加入组织带你一起进步成长。

共享变量

如果看过我前面文章的都知道,主内存中的共享变量会存储在CPU高速缓存= 》缓存条目=》缓存行中。

这里面有个点,就是缓存行才是存储数据的,但因为局部性原理,缓存行内的数据有可能不止有我们需要的共享变量比如X,在它的相邻的内存地址上有可能还有一个Y变量(就像数组中两个元素一样)。

伪共享

假设此时CPU1和CPU2 中相同缓存条目的缓存行内都存储了X和Y两个变量副本。如果CPU1中某个线程更新了共享变量X,此时CPU1会发出一个Invalidate消息到总线(MESI),使CPU2中包含了X的缓存条目被无效化。

此时CPU2的一个线程访问Y的时候,在高速缓存中发现缓存条目状态是无效话了,此时就属于缓存未命中,就需要从主内存从新加载数据。这种缓存共享的问题就叫做伪共享。

伪共享会导致缓存未命中,从而会降低处理器读、写操作的效率。

每日一个知识点:同步机制与内存屏障

阅读(547)

每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解,此处不涉及详细的原理性解读,只作为一种抛砖引玉。真正的理解一定是你自我研究探索所收获的知识。

爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方。

开源项目:

  • 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cubic

  • 摄像头视频流采集:https://gitee.com/sanjiankethree/cubic-video

  • Github 文章收录:https://github.com/qianglu1989/technology

同步机制与内存屏障

  • 获取屏障(LoadLoad、LoadStore的组合):

确保屏障之前的读操作优先于屏障之后的任何读、写操作被提交。

  • 释放屏障(LoadStore、StoreStore的组合):

确保屏障之前的读、写操作优先于屏障之后的任何写操作被提交。

重点:

volatile对可见性的保障是通过写线程和读线程配对使用存储屏障和加载屏障实现的

synchronized的实现方式与volatile的实现类似。虚拟机在monitorenter指令之后临界区之前插入获取屏障,在monitorexit字节码指令之前临界区结束之后插入释放屏障来确保临界区内的任何读写操作都不能被重拍到临界区外,从而保障临界区中的操作为一个原子操作

注意:x86处理器只支持StoreLoad重排序,所以x86处理器只需在volatile写操作后插入一个StoreLoad屏障就行了。而其他处理器需要在volatile读写前后插入适当的屏障。

爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方。

世界上最快的捷径,就是脚踏实地

微信公众号:架构技术专栏