什么是MySQL的内存,带你深入探寻数据库内存与Buffer Pool之间的关系

Mysql Server中的各种buffer你了解么? 区别于存储引擎,来看看server层有哪些特性在里面。 阅读详情

引言

MySQL是基于磁盘工作的,这句几乎刻在了每个后端程序员DNA里,但它真的对吗?其实答案并不能盖棺定论,你可以说MySQL是基于磁盘实现的,这点我十分认同,但要说MySQL是基于磁盘工作,这点我则抱否定的态度,至于为什么呢?这跟咱们本章的主角:Buffer Pool有关,Buffer Pool是什么?还记得咱们在《MySQL架构篇》中聊到的缓存和缓冲区么,其中所提到的写入缓冲区就位于Buffer Pool中。

不过在具体分析它之前,先结合MySQL的整体架构,来聊一聊MySQL的内存。

一、结合MySQL架构聊一聊它的内存

经过《MySQL架构篇》的学习后,咱们已经熟悉了MySQL的整体架构,也包括在《SQL执行篇》中,站在一条SQL的角度进一步加深了对各组件的印象,但其中并未讲明白MySQL的工作时的内存结构,哪本章就先来盘一盘这块内容。

闲话少说,先上个图,毕竟正所谓:“开局一张图,装备全靠打”,如下:

​注意观察,实际MySQL启动后内存结构略显复杂,但大体可分为MySQL工作组件、线程本地内存、MySQL共享内存、存储引擎缓冲区四大板块。

实际上MySQL内存模型和JVM类似,JVM内存主要会划分为线程共享区和线程私有区,而上图中的MySQL内存区域,左边则是线程私有区域,每条工作线程中都会分配的区域,各线程之间互不影响,而右边的三大板块,则属于线程共享区域,即所有线程都可访问的内存。

当然,线程共享区这块也会细分,右边上面的两个板块,都属于MySQL-Server层使用的内存,也就意味着这两块内存是所有引擎都共享的区域,而最下面这个区域,每个存储引擎都不相同,也就是InnoDB会构建自己的Buffer缓冲区,MyISAM也会构建自己的缓冲区。

Ok~,大概理解MySQL的内存构成后,下面一起简单聊一聊这四个内存区域。

1.1、MySQL Server - 工作组件

这个比较容易理解,也就是对应着MySQL架构图中的组件层,如下:

​因为后续客户端连接时,都需要经过一系列的连接工作,处理SQL时也需要经过一系列的解析、验证、优化工作,所以MySQL会在启动时,会先将这些工作组件初始化到内存中,方便后续处理客户端的操作。

这里很简单,只需要额外注意一点即可,就是数据库的连接池中,存的到底是什么?存的实际上就是数据库连接对象,MySQL内部的连接对象,其中包含了客户端连接信息,如客户端IP、登录的用户、所连接的DB....等这类信息,同时这些连接对象在内部会绑定一条工作线程,因此你也可以将它理解成是一个线程池!MySQL复用连接的本质,实则是在复用线程,出现一个新的客户端连接时,首先会根据客户端信息为其创建连接对象,然后再复用连接池中的空闲线程。

但MySQL工作线程的本地内存中包含了很多东西,所以下面简单聊一聊。

1.2、工作线程的本地内存

工作线程的本地内存区域,也被称之为线程私有区,即MySQL在创建每条线程时,都会为其分配这些内存:

​但这一大长排到底是啥意思呢,简单的说明一下其中每个区域的含义:

  • thread_stack:线程堆栈,主要用于暂时存储运行的SQL语句及运算数据,和Java虚拟机栈类似。

  • sort_buffer:排序缓冲区,执行排序SQL时,用于存放排序后数据的临时缓冲区。

  • join_buffer:连接缓冲区,做连表查询时,存放符合连表查询条件的数据临时缓冲区。

  • read_buffer:顺序读缓冲区,MySQL磁盘IO一次读一页数据,这个是顺序IO的数据临时缓冲区。

  • read_rnd_buffer:随机读缓冲区,当基于无序字段查询数据时,这里存放随机读到的数据。

  • net_buffer:网络连接缓冲区,这里主要是存放当前线程对应的客户端连接信息。

  • tmp_table:内存临时表,当SQL中用到了临时表时,这里存放临时表的结构及数据。

  • bulk_insert_buffer:MyISAM批量插入缓冲区,批量insert时,存放临时数据的缓冲区。

  • bin_log_buffer:bin-log日志缓冲区,《日志篇》提到过的,bin-log的缓冲区被设计在工作线程的本地内存中。

可以看到,在工作线程的本地内存中,除开最基本的线程堆栈外,MySQL还往内部“塞了”一堆东西,这些东西在不同的SQL运行时,都有各自的作用,但基本上是为了更好的保存临时数据而设计的。

思考一个问题:对于上面列出的各类缓冲区,为什么要为每条线程都分配专属的内存,而不是直接在共享内存中搞一块大的空间,然后提供给所有线程来操作呢?其实答案很简单,因为这些数据本身就是一条线程在执行SQL时产生的临时数据,其他线程压根不会去用到另一条线程的临时数据,所以这些临时数据没有必要被共享。

除开上述原因外,将这些缓冲区都放在线程本地内存中,还有一点最大的好处:能够提升多线程并发执行的性能!这句话怎么理解呢?很简单,如果把上述的各个缓冲区放在共享内存中,然后提供给线程存放执行时的临时数据,因为多线程的缘故,所以同一时刻、同一快内存有可能出现多条线程一起操作,那就会出现线程不安全的问题,想要解决就只能加锁将多线程串行化,这自然会在很大程度上影响性能!因此将这些存临时数据的缓冲区,设计在本地内存中才最合适。

不过也并非所有数据都适合放在线程的本地内存中,有一些多条线程之间都会访问的数据,如果再放到本地内存中,就会造成很大的冗余性,比如典型的索引根节点数据,每条线程都有可能会通过索引查询数据,因此每条线程都“缓存”一份放在自己的内存中,就会占用大量的内存空间,这样反而弊大于利。

下面再一起聊聊MySQL的共享内存区域,以及其中到底会存哪些数据。

1.3、MySQL共享内存区

​先简单介绍一下上述给出的几个内容:

  • Key Buffer:MyISAM表的索引缓冲区,提升MyISAM表的索引读写速度。

  • Query Cache:查询缓存区,缓冲SQL的查询结果,提升热点SQL的数据检索效率。

  • Thread Cache:线程缓存区,存放工作线程运行期间,一些需要被共享的临时数据。

  • Table Cache:表数据文件的文件描述符缓存,提升数据表的打开效率。

  • Table Definition Cache:表结构文件的文件描述符缓存,提升结构表的打开效率。

上面的这几个线程共享区域还比较容易理解,对于最后两个文件描述符缓存,大家可能存在些许疑惑,这里可以先看一下《FD-文件描述符的定义》,文件描述符本质上就是一个指针,指向一个具体的数据。这里为何要在内存中存放表数据文件、表结构文件的FD缓存呢?主要是为了提升性能,先来看一个问题:

比如我现在想要操作zz_users表的数据,那首先是不是得找到这张表?但表的位置可能分布在磁盘的任何一处,总不能触发磁盘IO把整个磁盘检索一遍,然后确定表的位置吧?所以内存中直接设计了一个缓存区,专门缓存这些表数据文件的磁盘位置,要对某张表进行操作时,直接去文件描述符缓存中找,然后根据其中记录的地址,去磁盘中固定的位置上操作表数据。

表结构的文件描述符缓存,作用也是相同的,比如现在要增加一个索引,或者修改一个字段,也不会把磁盘全部扫描一遍,而是直接根据内存中的文件描述符,去操作磁盘中对应位置的表结构文件。

但是这两个文件描述符缓存,更多的是为MyISAM引擎设计的,关于具体原因稍后再说。

1.3.1、MySQL8.x为什么移除了查询缓存?

OK~,再简单聊一下QueryCahce查询缓存,这块的设计思想是非常好的,也就是利用热点探测技术,对于一些频繁执行的查询SQL,直接将结果缓存在内存中,之后再次来查询相同数据时,就无需走磁盘,而是直接从查询缓存中获取数据并返回。

听起来似乎还不错呀,好像确实能带来不小的性能提升呢?但实则很鸡肋,为啥?看个例子。

select * from zz_users where user_id=1; select * from zz_users where user_id = 1; 复制代码

比如上述这两条SQL语句,在我们看来是不是一样的?确实,都是在查询ID=1的用户数据,但奇葩的事情出现了,MySQL的查询缓存会把它当做两条不同的SQL,也就是假如上面的第一条SQL,其查询结果被放入到了缓存中,第二条SQL依旧无法命中这个缓存,会继续走表查询的形式获取数据,Why?

因为MySQL查询缓存是以SQL的哈希值来作为Key的,上面两条SQL虽然一样,但是后面的查询条件有细微差别:user_id=1、user_id = 1,也就是一条SQL有空格,一条没有。

由于这一点点细微差异,会导致两条SQL计算出的哈希值完全不同,因此无法命中缓存,是不是很鸡肋?还有多种情况:user_id =1、user_id= 1,空格处于的前后位置不同,也会导致缓存失效。

也正是由于方方面面的原因,所以查询缓存在MySQL8.0中被完全舍弃了,即移除掉了查询缓存区,各方面原因如下:

  • ①缓存命中率低:几乎大部分SQL都无法从查询缓存中获得数据。

  • ②占用内存高:将大量查询结果放入到内存中,会占用至少几百MB的内存。

  • ③增加查询步骤:查询表之前会先查一次缓存,查询后会将结果放入缓存,额外多几步开销。

  • ④缓存维护成本不小,需要LRU算法淘汰缓存,同时每次更新、插入、删除数据时,都要清空缓存中对应的数据。

  • ⑤InnoDB引擎构建出的缓冲区中,也会类似的功能,因为与查询缓存也存在冲突。

因为上述一系列原因,再加上项目中一般都会使用Redis先做业务缓存,因此能来到MySQL的查询语句,几乎都是要从表中读数据的,所以查询缓存的地位就显得更加突兀,因此在高版本中就直接去掉了,毕竟弊大于利,带来的收益达不到设计时的预期。

1.4、存储引擎缓冲区

简单的讲明白线程共享区后,再来聊一聊存储引擎缓冲区,这块是本章重点,几乎任何存储引擎都会在启动时,向操作系统申请一块内存,用来作为缓冲区,每个引擎的缓冲区也并不相同,但有一点是共通的:即所有引擎的缓冲区,对于MySQL的工作线程而言,都是一块共享的内存区域。

现如今,MySQL众多存储引擎中,应用最为广泛的是InnoDB,所以我们就以InnoDB-Buffer Pool为例,在之前的MySQL章节中,我曾一度将其称之为「写入缓冲」,但要记住:BufferPool不仅仅只扮演「写入缓冲」的角色,其中主要包含了如下内容:

​同样先简单介绍一下其中每个区域的作用:

  • Data Page:写入缓冲区,主要用来缓冲磁盘的表数据,将写操作转移到内存进行。

  • Index Page:索引缓冲页,对于所有已创建的索引根节点,都会放入到内存,提升索引效率。

  • Lock Space:锁空间,主要是存放所有创建出的锁对象,详情可参考《MySQL锁机制实现原理》。

  • Dict Info:数据字典,主要用来存储MySQL-InnoDB引擎自带的系统表。

  • redo_log_buffer:redo-log缓冲区,存放写SQL执行时写入的redo记录。

  • undo_log_buffer:undo-log缓冲区,存放写SQL执行时写入的undo记录。

  • Adaptivity Hash:自适应哈希索引,InnoDB会为热点索引页,创建相应的哈希索引。

  • Insert Buffer:写入缓冲区,对于insert的数据,会先放在这里,然后定期刷写磁盘。

  • Lru List:内存淘汰页列表,对于整个缓冲池的内存管理列表(后续细聊)。

  • Free List:空闲内存列表,这里面记录着目前未被使用的内存页。

  • Flush List:脏页内存列表,这里主要记录未落盘的数据。

一顿看下来,其实Buffer Pool中内容还真不少,但估摸着大家看上面的释义,难免有些懵,这点先不急,后续段落会详细拆解,在此处先对这些内容有些印象即可,现在先说说为何各大存储引擎都会设计一个缓冲池吧。

虽然MySQL是基于磁盘存储数据的,但总不能每次读写操作都走磁盘吧?这样绝对会导致资源开销极大,同时性能也极低,因此各引擎都在内存中设计了一个缓冲池,用来提升数据库整体的读写性能。

而InnoDB引擎,是尤为特殊的存在,几乎将所有的操作都放在了内存中完成,因此慢慢的,InnoDB代替了MyISAM,成为了MySQL默认的存储引擎,不过还有其他方面的因素导致的,具体原因在下章:《MySQL引擎篇》中细聊。

二、InnoDB的核心 - Buffer Pool

刚刚聊到过,InnoDB引擎几乎将所有操作都放在了内存中完成,这句话主要是跟它的Buffer Pool有关,但Buffer Pool到底会占用多大内存呢?这点可以通过show global variables like "%innodb_buffer_pool_size%";指令查询,如下:

show global variables like "%innodb_buffer_pool_size%"; +-------------------------+----------+ | Variable_name | Value | +-------------------------+----------+ | innodb_buffer_pool_size | 44040192 | +-------------------------+----------+ 1 row in set (0.06 sec) 复制代码

在MySQL5.6版本以下,默认大小为42MB,而MySQL5.6以后的版本中,默认大小为128MB,这块内存是MySQL启动时向OS申请的一块连续空间。当然,我们也可以手动调整innodb_buffer_pool_size参数来控制,一般建议设置为机器内存的60~80%。

接下来咱们先把Buffer Pool中每个区域的具体作用说明白,也就是这张图:

​

2.1、数据页(Data Page)

InnoDB引擎为了方便读取,会将磁盘中的数据划分为一个个的「页」,每个页的默认大小为16KB,以页作为内存和磁盘交互的基本单位,而InnoDB的缓冲池也会以页作为单位,也就意味着:当InnoDB拿到申请的连续内存后,会按照16KB的尺寸将整块空间,划分成一个个的缓冲页。

在MySQL运行之初,这些划分出的缓冲页,都属于空闲页,也就是未使用的内存,随着运行时长的慢慢增长,会将磁盘中的数据页,一点点的载入内存当中,因为磁盘中的表数据是以16KB作为单位划分的,而内存中的缓冲页也是这个大小,因此发生一次磁盘IO读到的数据(读一页磁盘数据),会放入到一个缓冲页中存储,而这些承载磁盘数据的缓冲页,就被称之为数据页,其过程如下:

​当磁盘中的数据被载入到内存之后,带来的优势会极为明显:

  • 读数据时:如果在数据页中有,则直接会从内存中读取数据并返回,没有再去磁盘检索数据。

  • 写数据时:会先修改数据页的数据,修改后会标记相应的数据页,然后直接返回,再由后台线程去完成数据的落盘工作。

此时有没有发现:InnoDB的缓冲池,其实也具备「查询缓存」的功能~

不过MySQL会将哪些表数据放到缓冲池中呢?其实刚启动时里面并不会有数据,而是随着业务SQL的执行,一点点将磁盘中的数据加载进内存的,比如执行一条查询语句,因为最初内存中并没有加载数据页,因此会走磁盘检索数据,检索数据的过程中,不管此次IO读到的数据是不是目标数据,都会将它们放在内存中,而不是直接回收。

观察上述这个过程,这样做有什么好处呢?方便后续其他SQL要操作对应数据时,可以直接在内存中读到数据。

在条件允许,即内存充足的情况下,InnoDB会试图将磁盘中的所有表数据全部载入内存。

不过一般的机器,磁盘空间都会比内存要大出很多倍,所以当表数据较大时,也不可能无限制的载入,因而InnoDB会有一套完善的内存管理与淘汰机制,以此防止内存溢出风险(对于这点后续再详细阐述)。

2.2、索引缓冲页(Index Page)

上面讲到了,InnoDB会将部分乃至所有表数据载入内存,以此达到提升性能的目的,但不可能无限制载入,比如现在机器的内存为16GB,但磁盘中有30GB表数据,这显然无法放入进内存,所以无可避免的一点:在运行过程中,MySQL会走磁盘读数据。

比如一条查询语句要读的数据,在内存中没有相关的缓冲数据页,因此需要触发磁盘IO检索数据,但此时这条SQL可以命中索引,那会通过索引去查找数据,但问题来了!索引的根节点可能位于磁盘的任意位置,难道把磁盘的所有位置全部走一遍吗?这显然并不现实,所以InnoDB也会有对应的优化机制,即内存中也会缓冲索引页。

在MySQL启动时,就会将当前库中所有已存在的索引,其根节点放入到内存缓冲区中,因为索引的根节点只有16KB,因此就算目前库中就算创建了1000个索引,所有索引的根节点加起来占用的内存空间,也不过才1.5MB左右。将索引的根节点载入内存后,对于需要走索引查询的SQL,就会直接以相应的索引根节点为起始,然后去走索引查找数据,这样就避免了全盘查找索引根节点的这步操作。

Buffer Pool中有一块专门的区域:Index Page,专门用来存放载入的索引数据,存储这些数据的缓冲页,则被称之为索引页。随着运行时间的增长,也会将一些非根节点的索引页载入内存中,这是一种对于访问频率较高的索引页,专门推出的优化机制。

2.3、锁空间(Lock Space)

对于锁空间,相信认真看过《MySQL事务与锁原理篇》的小伙伴都不会陌生,咱们在其中讲锁机制的实现原理时,聊到过锁是基于事务实现,每个事务会生成自己的锁结构,而这些锁结构也同样需要空间来存储,而锁空间就是专门用来存储锁结构的一块内存区域。

但锁空间也不仅仅只会存储锁结构,还会存储一些并发事务的链表,例如死锁检测时需要的「事务等待链表、锁的信息链表」等。

锁空间一般都是有大小限制的,在《MySQL锁机制-锁粗化》中聊到过一种情况,当锁空间内存不足时,就会导致行锁粗化成表锁,以此来减少锁结构的数量,释放一定程度上的内存,但此时并发冲突就会变高!

2.4、数据字典(Dict Info)

对于数据字典估计大家很少有人接触过,毕竟这个是用来辅助InnoDB运行用的,咱们先思考一个问题,为啥我们可以通过SQL语句查询到库中的表信息、查询一张表的索引、约束等信息呢?如下:

-- 查询当前库中的所有表 show tables; -- 查询一张表的全部索引 show index from `tableName`; 复制代码

这些语句执行后都能查询出对应的信息,但这些信息咋来的呢?这首先跟MySQL的系统表有关,在InnoDB引擎中主要存在SYS_TABLES、SYS_COLUMNS、SYS_INDEXES、SYS_FIELDS这四张系统表,主要是用来维护用户定义的所有表的各种信息,如下:

  • SYS_TABLES:这张表中会存储所有引擎为InnoDB的表信息。 ID:一张表的ID号。 NAME:一张表的名称。 N_COLS:一张表的字段数量。 TYPE:一张表所使用的存储引擎、编码格式、压缩算法、排序规则等。 SPACE:一张表所位于的表空间。

  • SYS_COLUMNS:这张表用来存储所有用户定义的表字段信息。 TABLE_ID:表示一个字段属于那张表。 POS:一个字段在一张表中属于第几列。 NAME:一个字段的名称。 MTYPE:一个字段的数据类型。 PRTYPE:一个字段的精度值。 LEN:一个字段的存储长度限制。

  • SYS_INDEXES:这张表用来存储所有InnoDB引擎表的索引信息。 TABLE_ID:表示这个索引属于哪张表。 ID:一个索引的ID号。 NAME:一个索引的名称。 N_FIELDS:一个索引由几个字段组成。 TYPE:一个索引的类型,如唯一、联合、全文、主键索引等。 SPACE:一个索引的数据所位于的表空间位置。 PAGE_NO:这个索引对应的B+Tree根节点位置。

  • SYS_FIELDS:这张表用来存储所有索引的定义信息。 INDEX_ID:当前这个索引字段属于哪个索引。 POS:当前这个索引字段,位于索引的第几列。 COL_NAME:当前索引字段的名称。

这四张表也被称为InnoDB的内部表,这四张表在载入内存前,位于.ibdata文件中,在MySQL启动时会开始加载,载入内存后就会放入到Dict Info这块区域,当利用show语句查询表的结构信息时,就会在字典信息中检索数据。

2.5、日志缓冲区(Log Buffer)

InnoDB的缓冲池中,主要存在两个日志缓冲区,即undo_log_buffer、redo_log_buffer,分别对应着撤销日志和重做日志,但对于日志缓冲区就不过多介绍了,在《MySQL日志篇》中咱们已经反复说到过,它俩的作用主要是用来提升日志记录的写入速度,因为日志文件在磁盘中,执行SQL时直接往磁盘写日志,其效率太低了,因此会先写缓冲区,再由后台线程去刷写日志。

2.6、自适应哈希索引(Adaptivity Hash)

自适应哈希索引又是一个比较有趣的技术点,这种技术可以算的上是一种AI技术,哈希算法查找数据的效率非常高,在没有哈希冲突的情况下复杂度为O(1),而B+Tree检索数据的效率,取决于树的高度。建立索引时,只能选用一种数据结构来作为索引的底层结构:

  • 如果选择哈希结构,虽然效率高,但数据是无序的,因此不方便做排序查询。

  • 如果选择B+Tree结构,虽然有序,但查询的效率会受到树高的影响。

此时似乎陷入了两难的地步,两种结构各有优劣,但一般为了满足业务按序查询的需求,所以会折中选择B+Tree结构,虽然没有哈希索引那么快,但速度也还可以。

分析上述这个场景,明明选哈希结构的效率特别惊人,但就是不能用,这就好比你面前有一道绝世佳肴,但就不能吃一样,这显然令人十分难受。

而正是由于此原因,InnoDB创始人在研发时,就实现了一种名为自适应哈希索引的技术,在MySQL运行过程中,InnoDB引擎会对表上的索引做监控,如果某些数据经常走索引查询,那InnoDB就会为其建立一个哈希索引,以此来提升数据检索的效率,并且减少走B+Tree带来的开销,由于这种哈希索引是运行过程中,InnoDB根据B+Tree的索引查询次数来建立的,因此被称之为自适应哈希索引。

自适应哈希索引和普通哈希索引的区别在哪儿呢?普通哈希索引是在创建索引时将结构声明为Hash结构,这种索引会以索引字段的整表数据建立哈希,而自适应哈希索引是根据缓冲池的B+树构造而来,只会基于热点数据构建,因此建立的速度会非常快,毕竟无需对整表都建立哈希索引。

自适应哈希索引在InnoDB中是默认开启的,可以通过手动调整innodb_adaptive_hash_index参数来控制关闭,但一般尽量不要去关闭它,因为该技术能让MySQL的整体性能翻倍。

在MySQL8.0以下的版本中,如果同时删除一张大表的很多数据,有可能会因为自适应哈希索引的原因,造成线上MySQL出现抖动,不过该问题在MySQL8.x版本中已经被修复,但如若你的MySQL版本在此之下,那尽量不要在业务高峰期删除大量数据。

对于自适应哈希索引的使用情况,可以通过show engine innodb status \G;命令查看,但哈希索引由于自身特性的原因,因此也仅只能用于等值查询的场景,无法支持排序、范围查询。

2.7、写入缓冲区(Insert Buffer)

「Change Buufer写入缓冲」属于InnoDB的一大特性,其实「写入缓冲」在一开始被称之为「Insert Buffer插入缓冲」,也就是只对insert操作生效,到了MySQL5.5之后的版本中,才正式改为「写入缓冲」,对于insert、delete、update语句都可生效,那它的具体作用是干啥的呢?一起来简单的聊一聊。

结合前面聊过的「数据缓冲页」,咱们可以得知一点:如果要变更的数据页在缓冲区中存在,则会直接修改缓冲区中的数据页,然后标记一下变更过的数据页,但如果要操作的数据页并未被加载到缓冲区,那依旧会走磁盘去操作数据,走磁盘显然会影响性能,因此InnoDB就创造了一个「写入缓冲」。

以insert语句为例,不管在MySQL的任何版本中,执行一条插入语句之前,因为这条数据在磁盘中都不存在,因此缓冲区中自然也不可能会有对应的数据页,按照前面的说法,似乎必须走磁盘插入数据了对不?

「写入缓冲」出现的原因,就是为了解决此问题,当一条写入语句执行时,流程如下:

  • ①判断要变更的数据页是否被载入到内存。

  • ②如果内存中有对应的数据页,则直接变更缓冲区中的数据页,完成标记后则直接返回。

  • ③如果内存中没有对应的数据页,则将要变更的数据放入到「写入缓冲」中,然后返回。

此时会发现,不管内存中是否存在相应的数据页,InnoDB都不会走磁盘写数据,而是直接在内存中完成所有操作,但是要注意:并不是所有的写入动作,都可以在内存中完成,「写入缓冲」是有限制的,如下:

  • 插入的数据字段不能具备唯一约束或唯一索引。

为啥呢?因为如果存在唯一字段的表,在插入数据前必须要先判断表中是否存在相同值,一张表的数据不可能全部都载入数据,所以这个判断重复值的工作必须依赖磁盘中的表数据来完成,所以插入具备唯一性的数据时,就必须要走磁盘。

这里有小伙伴或许会疑惑了,那我表中会有一个主键呀,默认会存在一个主键索引,主键索引也是一种特殊的唯一索引,那不就意味着所有具备主键的表,都不能通过「写入缓冲」来插入数据呀?这点不一定,如果表的主键声明了是一个自增ID,那这个自增序列会由MySQL-Server自己来维护,因此ID会由MySQL来生成,是绝对不会出现重复值的,因此对于这种情况,会将要插入的数据放到「写入缓冲区」中。

那如果表中存在唯一索引、或者表的主键未声明是自增ID,难道插入数据时就不会用到这个「写入缓冲区」吗?答案是NO,依旧会用,为啥?先来回顾一下之前咱们讲过的《插入数据时索引的变化》,一条插入语句的执行过程如下:

  • ①先向聚簇索引中,插入一条相应的行记录(数据)。

  • ②对于非聚簇索引,都插入一个新的索引键,并将值指向聚簇索引中插入的主键值。

发现没有?插入数据时还需额外维护表中的次级索引,会为插入的新数据构建次级索引的索引键,并且将索引键插入到次级索引树当中,而这个过程就会用到「写入缓冲区」。

因为首先需要走一次磁盘,先插入行记录,插入完成后,假设表中存在三个非聚簇索引(次级索引),那难道再写三次磁盘维护次级索引吗?NO,对于不具备唯一性的索引,都会将要插入的索引键放在「写入缓冲区」。

对于修改、删除语句的执行,也是同理,那「写入缓冲区」中的数据究竟啥时候会真正写入到磁盘呢?

  • 当一条SQL需要用到对应的索引键查询数据时,会触发后台线程执行刷盘工作。

  • 当「写入缓冲区」内存空间不足时,会触发后台线程执行刷盘工作。

  • 当距离上一次刷盘的时间,间隔达到一定程度(默认10s),会触发后台线程执行刷盘工作。

  • MySQL-Server正在关闭时,也会触发后台线程执行刷盘工作。

上述这四种情况,都会导致后台线程执行刷盘工作,从而将数据真正的落入磁盘中存储。

到这里就已经将InnoDB缓冲池中,运行期间会出现的东西都讲明白啦,但最开始咱们提过一点,缓冲池是有大小限制的,毕竟内存有限,因此也不可能让咱们无限制的使用,那InnoDB是如何管理缓冲池内存的呢?接下来一起聊聊这个话题。

三、InnoDB缓冲池的内存是如何管理的?

InnoDB虽然在启动时,会将连续的内存划分为一块块的缓冲页,但这仅是逻辑上的划分,本质上所有的缓冲页之间,也是连续的内存。但随着MySQL在线上运行的时间越来越长,自然会导致这片连续的缓冲页变得七零八落,如下:

​当从磁盘加载一个数据页时,总不能将所有的缓冲页全部遍历一次,然后找到其中的空闲页放数据吧?这样难免有些影响性能,所以为了更好的管理缓冲池,InnoDB会为每个缓冲页创建一个控制块。

3.1、缓冲页的控制块是是个啥?

控制块是专门用于管理缓冲页而设计的一种结构,其中会包含:数据页所属的表空间、页号、缓冲页地址、链表节点指针等信息,所有的控制块都会放在缓冲池最前面,如下:

​当然,控制块也会占用缓冲池的内存空间,InnoDB会为每一个缓冲页都分配一个对应的控制块,后续InnoDB可以基于控制块去管理每一块缓冲页。

3.2、空闲页的管理

首先来聊聊对于空闲缓冲页的管理,为了能够更快的找到缓冲池中的空闲页,InnoDB会以控制块作为节点,将所有空闲的缓冲页组成一个空闲链表,也就是之前的Free链表,示意图如下:

​因为控制块对应着一个个的缓冲页,以控制块作为链表节点,也就等价于是由缓冲页组成的链表,在链表中会存在一个头结点,内部主要有三个值:

  • head:这是一根指针,指向空闲链表的第一个控制块。

  • tail:同样是一根指针,指向空闲链表的最后一个控制块。

  • count:这是一个数字,用来记录空闲链表的节点数量。

有了空闲链表后,会有什么好处呢?十分明显,当需要一块新的缓冲页存储磁盘数据时,不需要再去遍历所有缓冲页找一块空闲的出来了,而是直接找到空闲链表,根据空闲链表的指针,从中拿一块空闲缓冲页使用即可。

3.3、标记页的管理

咱们在前面提到过,当线程变更了内存中的数据页之后,会先对这个数据页做个标记,然后直接给客户端返回「执行成功」的响应。在这个过程中,被线程变更并标记过的数据页,则被称之为标记页,不过在有些地方也被称之为“脏页”。

对于内存中变更过的数据页,最终绝对是需要刷写到磁盘中的,前面也不止一次提到过,这个工作会由MySQL的后台线程完成,但问题又来了:当后台线程要刷盘时,它咋知道哪些数据页是变更过的呢?

有人也许会说,前面工作线程在执行完SQL之后,不是对数据页做了标记嘛?确实没错,但问题在于:缓冲池中的缓冲页那么多,后台线程难道去把所有缓冲页全部找一次,看看它有没有被标记嘛?

这样做确实可以,但还不够,因为每次刷盘都需遍历所有缓冲页,其过程的开销必然不小,比如刷盘时,缓冲池中就只有几个数据页发生了变更,为了刷写这几个页的数据就找一次所有页,这有点用迫击炮打鸟的意思在里面了。

因此为了后台线程刷盘时效率更高,InnoDB同样又创造了一个Flush链表,它的结构和Free链表一模一样,因此就不再画图了,两者的不同点在于:

  • Free链表:记录空闲缓冲页,为了使用时能更快的找到空闲缓冲页。

  • Flush链表:记录标记过的缓冲页,为了刷盘时能够更快的找到变更数据页。

当后台线程开始刷盘工作时,会直接找到Flush链表,然后直接将该链表中对应的缓冲页,其变更过的数据刷写到磁盘。

注意:标记页的刷盘时机与「写入缓冲」的刷盘时机相同,也包括「写入缓冲」也归属在Buffer Pool中,因此当「写入缓冲区」中有数据需要刷盘时,相应的缓冲页,同样会被加入Flush链表。

3.4、内存中的数据页是如何淘汰的?

Buffer Pool的内存空间是有限的,因此无法支撑所有数据源源不断的载入内存,所以InnoDB内部绝对有一套自己的淘汰机制,但先设想一个问题:所有数据都可以被淘汰吗?可以是可以,毕竟就算内存中的数据没了,磁盘中也会有数据,但是随机淘汰显然并不妥,我们希望做到是:那些频繁被访问的数据页可以长期驻留在内存中,一些很少被访问的数据页能够淘汰掉。

与Free空闲链表、Flush刷写链表相同,对于要可淘汰的数据页,也会被组合成一个LRU淘汰链表,但淘汰链表会由哪些缓冲页组成呢?首先对于空闲页和标记页是不会纳入淘汰范围内的,为啥?

  • 空闲页:本身这些缓冲页都没有被使用,内存都是空白的,淘汰空闲页没有任何意义。

  • 标记页:被标记过的缓冲页中,由于存在数据还未落盘,所以淘汰掉之后代表数据会丢失。

因此LRU链表是由已使用、但未曾变更过的缓冲页组成的,不过要注意:有些数据页会在Flush、LRU两个链表之间“跳动”:

  • 当LRU链表中的一个数据页发生变更后,会从LRU链表转到Flush链表。

  • 当标记页中的变更数据落盘后,此时标记页又会从Flush链表回到LRU链表。

有些地方会存在些许误区,也就是标记页(脏页)也会被放入LRU链表中,这显然是不对的,为啥?因为所有的链表都是由控制块作为节点构建的,而一个控制块中只有一根指针,也意味着一个控制块同时只能加入一个链表中,所以就不可能出现一个缓冲页,既处于LRU链表,又位于Flush链表中。

OK~,讲明白上面这点误区后,接着先说明一下淘汰的含义:淘汰是指将一个已使用的缓冲页,其中的所有数据清空,使其变为一个空闲页。

理解淘汰的含义后,再说说InnoDB的淘汰机制是怎么样的呢?很简单,和互联网大厂中的淘汰手段相同!即末尾淘汰机制。

3.4.1、末尾淘汰机制

互联网大厂并不像国企、编制这类铁饭碗,为了防止“蛀虫”产生,会不断的吸纳新鲜血液,有新人进,自然也就会有老人出,但Boss又不希望将哪些工作认真、技术过硬、成绩优异的精英淘汰掉,所以一个著名的手段:末尾淘汰机制就诞生了。

一般企业中都会有KPI绩效考核制度,一个员工的工作态度、工作成绩都会计算在KPI中,当企业的人员数量过于臃肿时,就会开启一轮淘汰环节,所有员工中会淘汰哪一部分人呢?也就是按员工的综合KPI来决定,一些长期保持吊车尾的员工,则会面临被“优化”的风险。

这种机制的好处在于:对于企业有用的精英们,永远都能够留下来,而对于一些成绩平平、工作态度不认真的员工,则会被淘汰出局,活生生一副弱肉强食的森林法则。

末尾淘汰机制虽然很残酷,但对于企业而言确实很管用,所以InnoDB中的淘汰机制亦是如此,先来聊聊最基本的末尾淘汰机制:

  • 当一条线程来读写数据时,命中了缓冲区中的某个数据页,那就直接将该页挪到LRU链表最前面。

  • 当未命中缓冲数据页时,需要走磁盘载入数据页,此时内存不够的情况下,会淘汰链表末尾的数据页。

从磁盘载入的数据会直接插入到LRU链表头部,也就是直接将其设置为链表的第一个节点。

这里也许有小伙伴会疑惑:似乎缓冲区中的数据页会一直频繁移动呐,这不会影响性能吗?答案是并不会,为啥呢?因为这里是链表结构而并非数组结构,将一个缓冲页移动到其他链表中,或将一个缓冲页移动到链表最前面,实际上只需要改一下指针即可,无需真正的触发数据挪动的工作。

OK~,举个简单的例子感受一下InnoDB的末尾淘汰机制!假设此时LRU链表由8个缓冲页组成,并且此时缓冲区空间已满,如下:

​

此时假设一条查询语句,命中了其中的第6个数据页,此时这个数据页会被挪到最前面:

​

此时又来了一条SQL,要操作的数据在缓冲区不存在,因此会从磁盘读取数据并载入内存,但因为目前缓冲区已经满了,所以需要淘汰一个缓冲页,用来存放载入的新数据,此时就会将末尾的数据页淘汰,如下:

​

上面这个过程列出了最简单的末尾淘汰机制,但这种方式会存在两个较为致命的问题:

  • ①利用局部性原理预读失效时,会导致数据页常驻缓冲区。

  • ②查询数据量过大时,会导致缓冲区中的热点数据全部被替换,导致缓冲池被“污染”。

3.4.2、预读失效问题

在讲《索引底层实现原理》时,曾详细的描述过磁盘IO的执行过程,在其中提到了一种利用“局部性原理预读数据”的机制,一般来说,当程序读取某块数据时,这块区域附近的数据也很有可能被读取,因为程序在存储数据时,都会将一个数据保存在一块连续的空间中,因此MySQL在读取数据时,默认会使用局部性思想预读数据,也就是读取一个数据时,默认会将其附近的16KB数据一次性全部载入内存。

刚刚的案例中讲到过,当数据载入内存后会分配一个缓冲页来存放,并且会将相应的数据页放在LRU链表的最前面,记住!这个数据页一共是有16KB数据的,也就意味着里面会有多行表数据,假设此时程序只读取了这页数据中的一行记录,对于其他数据并不需要读取,这也就是所谓的预读失效问题。

预读失效:即MySQL利用局部性原理预读载入的数据,在接下来时间内并未被使用。

大家想想啊,如果按照前面列举的那种末尾淘汰机制去载入数据,一页数据被载入后会放到链表的头部,那想要淘汰这个数据页还需要等很长很长一段时间,毕竟MySQL实际会划分出几千几万个缓冲页,把这个没用的数据页放在了最前面,也就意味着该数据页会占用缓冲页很长时间。

为了解决这个问题,InnoDB并未采用最基本的末尾淘汰算法,而是对其做了些许优化,会将整个LRU算法划分为old、young两个区域组成。

等等,old、young?这是不是很耳熟?熟悉JVM虚拟机的小伙伴应该知道,在JVM的内存模型中,也有类似的概念,所以其实到这里大家会发现,所有技术的底层大致都是共通的!

young、old两个区域在LRU链表中的占比,默认为63:37,你也可以通过innodb_old_blocks_pc这个参数,来手动调整old区在整个LRU链表中的占比。

默认不改的情况下,假设LRU链表中由100缓冲页构成,那么前63个属于young区,后37个属于old区,示意图如下:

​

LRU链表被划分为两个区域后,从磁盘中预读的数据页,就只需要加入到old区域的头部,当这个数据页被真正访问时,才会将其插入young区的头部。如果预读的这页在后续一直没有被访问,就会从old区域移除,从而不会影响young区域中的热点数据。

也就是说,在划分为两个区域后,young区域是用来存储真正的热点数据页,而old区则是用来存放有可能成为热点数据页的“候选人”,当需要淘汰缓冲页时,会优先淘汰old区中的数据页,毕竟young区中留下的都是久经考验的精英!

3.4.3、缓冲池污染问题

InnoDB将LRU链表划分为两个区域后,改善了预读失效带来的问题,但还不够,因为还有可能会出现缓冲池污染的问题,这又是啥意思呢?

此时假设一条线程在执行SQL语句,目前是需要查询一张百万级别的所有表数据,由于Buffer Pool空间有限,所以如果按照原本的淘汰规则来清理内存,这次查询过程可能会导致Buffer Pool里面的所有热点数据全部被换出。等这次查询结束后,内存中只剩下了这次查询载入的数据页,当有线程访问原本哪些热点数据时,由于缓冲区中的数据页被换出了,因此就会产生大量的磁盘IO。

上述这个过程,则被称之为Buffer Pool污染问题,但要注意:并不是需要查询大量结果才会导致这个问题出现,而是当扫描的数据过多时,都会引发此问题,比如典型的对大表执行了全表扫描,因为在扫描的过程,会不断从磁盘载入新的数据页放在内存中。

InnoDB为了解决该问题,又引入了一种新的技术,名为young区晋升限制,是不是有点耳熟?在JVM中,为了防止新生代过早晋升年老代,从而频繁触发FullGC的问题,在设计时也有晋升条件限制,默认情况下,一个对象只有达到了15岁之后,才能从新生代晋升年老代,毕竟能够熬过16轮新生代GC的对象,也绝对不会无缘无故突然挂掉。

而InnoDB中的young区晋升限制,同样是这个原理,毕竟上面的全表扫描案例中,很多数据页只会被访问一次,但是由于需要访问它,所以才被载入了内存,最终导致old区放不下,从而导致了young区的热点数据被替换。

而加入了young区的晋升限制后,就能有效避免这种访问一次的数据页过早进入young区,InnoDB是怎么做的呢?其实很简单,就是加了一个停留时间的限制,如果一个数据页想从old区晋升到young区,必须要在old区中存活一定时间,这个时间默认为1s/1000ms,也可以通过参数innodb_old_blocks_time调整。

思考一下,由于存在这个时间限制,所以old区的数据页,想要进入young区,就必须达成两个条件: ①在old区中停留的时间超过了1000ms。 ②在old区中,一秒后有线程再次访问了这个数据页。

上面的第一条还比较容易理解,但第二条估计有些懵,啥意思啊?其实很简单,结合前面的淘汰算法:一个刚被载入的数据页,会先放到old区的头部,当该数据页被二次访问后才会挪到young区的头部。

那为啥又要等到一秒之后再次访问了才行呢?因为如果一个数据页被载入内存后,必须要先能撑住1s才行!

OK~,通过这种晋升限制的方式,就能完美的解决全表扫描引起的缓冲池污染问题,这也是InnoDB最终的淘汰机制,当一个缓冲页的数据被淘汰后,也就是一个缓冲页的数据被清空后,会将其再次加入到Free空闲链表中等待分配。

四、MySQL内存篇总结

经过上述一系列的分析后,咱们就将MySQL内存方面的知识理清楚了,尤其是关于InnoDB的缓冲池,会发现和我说的一样:InnoDB引擎几乎将所有的操作都放在了内存中进行,比如写日志、写数据、查数据等,只有逼不得已的情况下,才会走磁盘读写数据。

假设你部署MySQL的机器内存足够大,并且为Buffer Pool分配的内存空间也足够大,比如机器的内存有128GB,此时为Buffer Pool分配了100GB,而整个库的所有表数据加起来仅有80GB,此时要记住!InnoDB几乎会将所有的表数据全部载入内存,后续所有的读写操作都会基于内存+后台线程刷盘的方式进行。

到这里大家会发现,虽然InnoDB是一款基于磁盘研发的存储引擎,但它几乎将内存的使用开发到了极致,能在内存完成的就压根不会走磁盘,在最大程度上提升MySQL的整体性能。

OK~,最后稍微总结一下InnoDB内存管理这块的内容,InnoDB采用三个链表结构来管理所有的缓冲页:

  • Free链表:统一管理、分配所有未使用的缓冲页。

  • Flush链表:统一管理、刷写所有被标记过的缓冲页。

  • Lru链表:统一管理、淘汰所有已使用、未变更过的缓冲页。

在内存的淘汰机制方面,InnoDB基于末尾淘汰机制做了两点改善:

  • ①将Lru链表划分为了young、old两个分区,用来解决预读失效导致的内存占用问题。

  • ②引入了young区的晋升限制,解决了全表扫描时,young区的热点数据页被换出的问题。

至此,《MySQL内存篇》就告一段落啦~,在本篇中几乎讲到了MySQL、InnoDB内存使用的方方面面,当然,其他的存储引擎涉及不是太深,因此并为做过多的分析与讲解,大家对其他引擎的缓冲区感兴趣,也可以自行研究。

十二)MySQL之内存篇:深入探寻数据库内存与Buffer Pool的奥妙! 经过上述一系列的分析后,咱们就将MySQL内存方面的知识理清楚了,尤其是关于InnoDB的缓冲池,会发现和我说的一样:InnoDB引擎几乎将所有的操作都放在了内存中进行,比如写日志、写数据、查数据等,只有逼不得已的情况下,才会走磁盘读写数据。假设你部署MySQL的机器内存足够大,并且为分配的内存空间也足够大,比如机器的内存有128GB,此时为分配了100GB,而整个库的所有表数据加起来仅有80GB,此时要记住!InnoDB。 阅读详情

相关推荐

MySQL内存使用率高且不释放问题排查与总结

总结一下MySQL内存使用率高且不释放的应对方法: 继续加大内存(如果参数调无可调时选择); 修改减小innodb_buffer_pool_size参数(牺牲一定innodb性能); 排查消耗内存的慢SQL,及时优化; 检查相关session参数是否设置合理,比如join_buffer_size、query_cache_size是否设置过大; 使用gdb回收内存碎片(生产环境谨慎操作):gdb --batch --pid ‘pidof mysqld’--ex 'call malloc_trim(0)';

h363659487的博客 1万+

mysql内存相关知识介绍

本文产生的背景是此前项目上的一次线上故障,在我司DBA的协助下得到了处理,但是也发现了自己对于mysql的了解之匮乏,这里对新了解到的知识进行初步的总结介绍。

CRUD工程师 4230

一张图弄清mysql内存结构

一张图弄清mysql内存结构,内存中都有什么,多mysql原理及后续问题排查、优化很有帮助,推荐给大家

MySQL学习笔记(二)InnoDB内存模型与磁盘同步机制

当我们将数据存放到内存,这时候我们的系统中将存在两分数据,一份在内存,一份在磁盘,这时候如果我们对数据进行操作,可能会导致两边数据不同步,那我们就需要对两边数据进行同步处理。的大小是固定的,写入的日志数量是有限制的,并且它的目的只是去保证数据不丢失,数据落盘了,这些日志就没有用了,所以 RedoLog采用的是循环覆盖写的方式。RedoLog的目的,是去保证我的数据页在内存,但是还没有同步到磁盘的时候,宕机导致的数据丢失。是一种顺序IO,纪录了哪个页,哪个磁盘哪个扇区,哪个offset的变更,是循环写入的。

qq_36509457的博客 1376

逆变器专题(6)-正负序分离(二阶广义积分器DSOGI)

相应仿真原件请移步资源下载DSOGI作为一种常用的正负序分离方法,其可以在电压三相不平衡的状态下实现较为精准的锁相环。

cyc19980105的博客 8808

【AI Infra】Perfetto性能分析工具指南

Perfetto是一款开源性能分析工具,提供系统级和应用级的活动数据追踪功能。其界面分为四个区域:左侧操作区(包含Trace查看、SQL查询、预置分析等功能)、上方信息区、中间图形化展示区以及下方详细信息区。工具支持快捷键操作(w/s缩放、a/d移动、m标记等),并能高效处理大体积Trace文件。通过Timeline分析可发现性能瓶颈,如案例中update_actor耗时过高的问题,最终定位到CPU offloading导致的MemcpyH2D操作耗时过长,提出了优化方案。Perfetto相比Systrac

baiweizeng的博客 1511

(十二)MySQL之内存篇:深入探寻数据库内存与Buffer Pool的奥妙!

经过上述一系列的分析后,咱们就将MySQL内存方面的知识理清楚了,尤其是关于InnoDB的缓冲池,会发现和我说的一样:**InnoDB引擎几乎将所有的操作都放在了内存中进行**,比如写日志、写数据、查数据等,只有逼不得已的情况下,才会走磁盘读写数据。假设你部署MySQL的机器内存足够大,并且为分配的内存空间也足够大,比如机器的内存有128GB,此时为分配了100GB,而整个库的所有表数据加起来仅有80GB,此时要记住!InnoDB。

weixin_54542328的博客 1135

AI颠覆市场调研:生成式智能体如何重构商业决策?

AI正在将市场调研从"成本中心"转变为"战略武器"。当企业能够用生成式智能体实时模拟百万级虚拟用户时,传统调研的样本偏差、时间滞后、成本高昂等问题将不复存在。对于开发者而言,这不仅是技术的狂欢,更是参与商业规则重构的历史机遇。行动建议立即体验Simile/Aaru的智能体平台,验证虚拟样本效果参与LLM持续性记忆架构的开源项目(如Memory Llama)关注AI调研API的标准化进程(如Hugging Face的Market Research Toolkit)

这里汇聚了前沿的技术分享与实用的开发技巧,带你探索从创意到企业的技术创业之路。 617

【MySQL进阶】浅谈InnoDB中的BufferPool

【MySQL进阶】浅谈InnoDB中的BufferPool

blblccc 1043

医疗影像分割 | 使用MedSAM训练自己的医学图像分割数据集

摘要 本文详细介绍了使用MedSAM模型训练自定义医学图像数据集的完整流程。主要内容包括:代码下载与环境配置(创建虚拟环境、安装依赖、验证CUDA)、数据集准备(目录结构、格式要求、命名规范及检查工具)、预训练模型下载(不同模型选择建议),以及训练脚本修改(解决文件名匹配、Windows系统NCCL支持、单GPU训练等常见问题)。通过逐步指导,帮助用户快速搭建MedSAM训练环境并适配自己的医学图像数据,实现高效的分割模型训练。

LiRongLu_的博客 490

Arduino与Proteus仿真实例-HG7881(L9110)驱动直流电机仿真

HG7881(L9110)驱动直流电机仿真 HG7881 (L9110) 双通道电机驱动器模块。 HG7881 (L9110) 是一款紧凑型电机驱动芯片,支持 2.5-12V 电压范围,800mA 连续电流。 这些芯片具有内置输出钳位二极管以保护敏感的微控制器电子设备。 它们适用于非常小的机器人项目。 每个 HG7881 (L9110) 芯片都能够使用两个数字控制输入驱动单个直流电机。 一个输入用于选择电机方向,而另一个用于控制电机速度。 速度是通过使用 PWM 脉冲宽度调制来控制的。 电机驱动器通常具有

视觉与物联智能 3051

MySQL - InnoDB 内存结构解析

Buffer Pool:缓冲池,简称 BP。其作用是用来缓存表数据与索引数据,减少磁盘 IO 操作,提升效率。Buffer Pool 由缓存数据(Page) 和对缓存数据页进行描述的控制块组成,控制块中存储着对应缓存页的所属的表空间、数据页的编号、以及对应缓存页在 Buffer Pool 中的地址等信息。Buffer Pool 默认大小是 128M, 以 Page 页为单位,Page 页默认大小 16K,而控制块的大小约为数据页的5%,大概是800字节。

z1ztai的博客 413

RAGFlow 系列教程 第14课:GraphRAG -- 知识图谱增强检索

RAGFlow v0.25.0深度解读了GraphRAG技术,通过构建知识图谱解决传统RAG在多跳推理问题上的局限。系统支持General GraphRAG和Light GraphRAG两种实现,包含四阶段构建流程:实体关系抽取、实体消解、社区检测和图持久化。核心代码位于rag/graphrag目录,采用LLM驱动的抽取机制,结合编辑距离和LLM确认的实体消解策略,以及Leiden社区检测算法。KGSearch模块通过多信号融合检索(实体+关系+N-hop+社区)实现高效查询。该系统将非结构化文本转化为可推

weixin_43674738的博客 101

MySQL内存模型与相关技术

本文主要描述了MySQL的内存模型结构以及其每块内存的作用,其中还讲述了一部分MySQL内存相关的技术实现和原理

qq_46863837的博客 946

ESP8266-Arduino网络编程实例-异步TCP客户端

本次实例将使用ESPAsyncTCP库实现一个简单的客户端。

视觉与物联智能 1741

从生产订单到整车交付,SAP 离散制造在汽车行业里的真实落点

汽车行业谈数字化,很容易一上来就谈智能工厂、数字孪生、AI 排产、无人仓、视觉质检。可落到 SAP 项目现场,真正把工厂撑起来的,往往还是几个很朴素的问题,客户订单来了以后,BOM 能不能准确展开,物料能不能准时到线,产能有没有被过度承诺,生产订单能不能被车间正确执行,质量问题能不能追溯,成本能不能按工单和批次沉淀下来。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 115

图解mysql(六)——内存篇

图解mysql(六)——内存篇

yttt的博客 2073

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

数据库系列之MySQL数据库中内存使用分析

在实际系统环境中,MySQL实例的内存使用随着业务的增长缓慢增长,有些时候并没有及时的释放。本文简要介绍下MySQL数据库中和内存相关的配置,以及分析内存的实际使用情况,以进行应急和调优处理。

牧羊人的方向 2667

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

(十九)大白话一文总结初步了解到的MySQL存储模型以及数据读写机制

本文主要讲了MySQL存储模型以及数据读写机制

zht245648124的博客 692

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

mysql数据库内存占用高原因(数据库内存占用高)

mysql数据库内存占用高原因

eagle89的专栏 5188

简述一下 的内存模型

结合之前的上下文,我们一直在讨论 MySQL 主从复制的问题,我猜测你可能想问 **MySQL 的内存模型**。MySQL 的内存模型主要分为**全局内存**和**会话内存**两部分,涉及服务器级别的共享资源和每个客户端连接的私有资源。- **动态分配**:全局内存(如 Buffer Pool)在启动时分配,可动态调整;- **主库**:二进制日志缓存(`binlog_cache_size`)存储待发送的事件。#### 2. **会话内存(Thread-Specific Memory)**

2401_85327573的博客 545

MySQL内存

注意,修改这些参数可能会对MySQL的性能和内存占用产生影响,因此在调整之前建议进行适当的测试和性能监控。是MySQL数据库中一个重要的配置参数,它用于设置InnoDB存储引擎的缓冲池大小。请注意,具体的参数名称和用法可能因MySQL的版本和配置而有所不同。如果你使用的是其他存储引擎,如MyISAM,则需要考虑相应的参数来优化内存使用。建议在调整任何MySQL配置参数之前,先进行备份并进行适当的测试,以确保性能优化的效果和稳定性。参数指定了InnoDB缓冲池的大小,它决定了可以缓存的数据和索引的数量。

喝醉酒的小白 1246

mysql内存相关的配置

mysql内存相关的配置,mysql内存怎么配置,Innodb_buffer_pool怎么配置,change buffer怎么配置

jkzyx123的博客 4311

数据库之内存管理--SimpleDB

数据库之内存管理--SimpleDB

zszyejing的博客 970

mysql内存机制_MySQL内存管理机制

1. BufferPoolWhat is BufferPool?MySQL InnoDB Buffer Pool,MySQL InnoDB 缓冲池。里面缓存着大量数据(数据页),使 CPU 读取或写入数据时,不直接和低速的磁盘打交道,直接和缓冲区进行交互,从而解决了因为磁盘性能慢导致的数据库性能差的问题。Why need BufferPool?buffer pool 最主要的功能便是加速读和加速写...

weixin_32025789的博客 684

Mysql存储模型

表、字段、行都是Mysql中的逻辑概念,那对应的物理概念是什么呢?Mysql实际上是如何存储数据的呢?

weixin_35794878的博客 712
上一篇: 浅谈解析一下怎么去区分JVM 内存区域的划分
下一篇: 8000字带你了解Java开发中有哪些常用的注解与它们的作用
Java_LingFeng
博客等级 码龄4年 505粉丝 259原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值