其实存储引擎也很简单,我认为就是一种存储解决方案,实现了新增数据、更新数据和建立索引等等功能。
有哪些已有的存储引擎可以让我们选择呢?
InnoDB、MyISAM、Memory、CSV、Archive、Blackhole、Merge、Federated、Example
种类很多,但是常用的存储引擎目前就只有InnoDB和MyISAM,下面将会介绍到InnoDB存储引擎。
InnoDB存储引擎有多个内存块,这些内存块组成了一个大的内存池。后台线程主要负责刷新内存池中的数据、将已修改的数据刷新到磁盘等等。接下来我们分别介绍后台线程和内存池。
InnoDB后台有多个不同的线程,用来负责不同的任务。主要有如下:
InnoDB的内存架构主要分为三大块:
InnoDB为了做数据的持久化,会将数据存储到磁盘上。但是面对大量的请求时,CPU的处理速度和磁盘的IO速度之间差距太大,为了提高整体的效率, InnoDB引入了缓冲池。
当有请求来查询数据时,如果缓存池中没有,就会去磁盘中查找,将匹配到的数据放入缓存池中。同样的,如果有请求来修改数据,MySQL并不会直接去修改磁盘,而是会修改已经在缓冲池的页中的数据,然后再将数据刷回磁盘,这就是缓冲池的作用,加速读,加速写,减少与磁盘的IO交互。
缓冲池说白了就是把磁盘中的数据丢到内存,那既然是内存就会存在没有内存空间可以分配的情况。所以缓冲池采用了LRU算法,在缓冲池中没有空闲的页时,来进行页的淘汰。但是采用这种算法会带来一个问题叫做缓冲池污染。
LUR算法
在 MySQL 中,LRU 指的是 Least Recently Used 缓存淘汰算法。该算法用于在缓存空间不足时,通过淘汰最近最少使用的数据来腾出空间,以便让新的数据进入缓存。
MySQL 中常用 LRU 算法来管理缓存,例如 InnoDB 存储引擎就使用了 LRU 算法来管理其缓冲池(Buffer Pool)。InnoDB 的缓冲池是一个内存区域,用于缓存数据库中的数据页,通过缓存页,可以避免频繁读取磁盘,从而提高数据库的性能。当需要获取一个数据页时,InnoDB 会首先在缓冲池中查找,如果找到了就直接返回,否则会从磁盘中读取并放入缓冲池中。
当缓冲池空间不足时,InnoDB 就需要根据 LRU 算法来淘汰一些数据页。具体来说,InnoDB 会维护一个“最近使用列表”(Recently Used List)和一个“最近未使用列表”(Recently Unused List),最近使用的数据页会被移到最前面,最近未使用的数据页则会移到后面。当需要淘汰数据页时,InnoDB 会选择最近未使用的数据页进行淘汰,以便让更常用的数据页留在缓冲池中,从而提高缓存命中率。
需要注意的是,LRU 算法是一种基于历史访问模式的淘汰算法,它假设未来的访问模式会和过去的访问模式相似,因此会尽可能地保留最近使用的数据。但是,在实际应用中,访问模式并不一定会保持不变,因此 LRU 算法也有其局限性。针对这个问题,还有其他缓存淘汰算法,如 MRU(Most Recently Used)、LFU(Least Frequently Used)等,可以根据具体场景来选择合适的算法。
缓冲池污染
缓冲池污染(Buffer Pool Contention)是数据库系统中的一个性能问题,指的是多个事务同时访问和争用缓冲池中的同一数据页,从而导致了事务之间的等待和竞争,以及数据库性能下降。缓冲池污染通常发生在高负载的数据库系统中,它可能会导致系统响应变慢,甚至服务不可用。
在一个并发访问的环境中,当多个事务同时需要访问或修改同一个数据页时,它们都需要先将该数据页读入到缓冲池中,再进行读取或修改操作。由于缓冲池的大小有限,当缓冲池中的数据页被占满后,新的事务需要等待已有事务归还缓冲池中的数据页才能继续执行,这就是缓冲池的竞争现象。
缓冲池污染的另一个原因是缓冲池管理算法的问题,常见的缓冲池管理算法包括最近最少使用(LRU)、先进先出(FIFO)等,它们都是基于一些策略来选择要从缓冲池中淘汰的数据页。如果选择的策略不够优秀,就会导致缓冲池中的热点数据页被频繁淘汰,从而影响系统性能。
当你在进行批量扫描甚至全表扫描时,可能会将缓冲池中的热点页全部替换出去。这样以来可能会导致MySQL的性能断崖式下降。所以InnoDB对LRU做了一些优化,规避了这个问题。
MySQL采用日志先行,在真正写数据之前,会首先记录一个日志,叫Redo Log,会定期的使用CheckPoint技术将新的Redo Log刷入磁盘,这个后面会讲。
除了数据之外,里面还存储了索引页、Undo页、插入缓冲、自适应哈希索引、InnoDB锁信息和数据字典。下面选几个比较重要的来简单聊一聊。
插入缓冲针对的操作是更新或者插入,我们考虑最坏的情况,那就是需要更新的数据都不在缓冲池中。那么此时会有下面两种方案。
很明显,第二种方案要好一点,减少了与磁盘IO的交互。
两次写
鉴于都聊到了插入缓冲,我就不得不需要提一嘴两次写,因为我认为这两个InnoDB的特性是相辅相成的。
插入缓冲提高了MySQL的性能,而两次写则在此基础上提高了数据的可靠性。我们知道,当数据还在缓冲池中的时候,当机器宕机了,发生了写失效,有Redo Log来进行恢复。但是如果是在从缓冲池中将数据刷回磁盘的时候宕机了呢?
这种情况叫做部分写失效,此时重做日志就无法解决问题。
在刷脏页时,并不是直接刷入磁盘,而是copy到内存中的Doublewrite Buffer中,然后再拷贝至磁盘共享表空间(你可以就理解为磁盘)中,每次写入1M,等copy完成后,再将Doublewrite Buffer中的页写入磁盘文件。
有了两次写机制,即使在刷脏页时宕机了,在实例恢复的时候也可以从共享表空间中找到Doublewrite Buffer的页副本,直接将其覆盖原来的数据页即可。
自适应索引就跟JVM在运行过程中,会动态的把某些热点代码编译成Machine Code一样,InnoDB会监控对所有索引的查询,对热点访问的页建立哈希索引,以此来提升访问速度。
为了提高磁盘操作性能,当前的数据库系统都采用异步IO的方式来处理磁盘操作。InnoDB也是如此。
与AIO对应的是Sync IO,即每进行一次IO操作,需要等待此次操作结束才能继续接下来的操作。但是如果用户发出的是一条索引扫描的查询,那么这条SQL语句可能需要扫描多个索引页,也就是需要进行多次IO操作。在每扫描一个页并等待其完成再进行下一次扫描,这是没有必要的。用户可以在发出一个IO请求后立即再发出另外一个IO请求,当全部IO请求发送完毕后,等待所有IO操作完成,这就是AIO。
AIO的另外一个优势是进行IO Merge操作,也就是将多个IO合并为一个IO操作,这样可以提高IOPS的性能。
在InnoDB 1.1.x之前,AIO的实现是通过InnoDB存储引擎中的代码来模拟的。但是从这之后,提供了内核级别的AIO的支持,称为Native AIO。Native AIO需要操作系统提供支持。Windows和Linux都支持,而Mac则未提供。在选择MySQL数据库服务器的操作系统时,需要考虑这方面的因素。
MySQL可以通过参数innodb_use_native_aio来决定是否启用Native AIO。在InnoDB存储引擎中,read ahead方式的读取都是通过AIO完成,脏页的刷新,也是通过AIO完成。
InnoDB存储引擎在刷新一个脏页时,会检测该页所在区(extent)的所有页,如果是脏页,那么一起刷新。这样做的好处是通过AIO可以将多个IO写操作合并为一个IO操作。该工作机制在传统机械磁盘下有显著优势。但是需要考虑下吧两个问题:
是不是将不怎么脏的页进行写入,而该页之后又会很快变成脏页?
固态硬盘有很高IOPS,是否还需要这个特性?
为此InnoDB存储引擎1.2.x版本开始提供参数innodb_flush_neighbors来决定是否启用。对于传统机械硬盘建议使用,而对于固态硬盘可以关闭。
上面聊过,InnoDB中缓冲池中的页数据更新会先于磁盘数据更新的,InnoDB也会采用日志先行(Write Ahead Log)策略来刷新数据,什么意思呢?当事务开始时,会先记录Redo Log到Redo Log Buffer中,然后再更新缓冲池页数据。
Redo Log Buffer中的数据会按照一定的频率写到重做日志中去。被更改过的页就会被标记成脏页,InnoDB会根据CheckPoint机制来将脏页刷到磁盘。
Redo Log 日志详解
InnoDB在对一些数据结构本身的内存分配时,需要从额外的内存池中进行申请。例如缓冲池中的中的一些对象记录了锁、等待、LUR等消息,这些对象需要从额外的内存池中申请内存。
说完缓冲池,下面说CheckPoint技术。
CheckPoint技术是用来解决如下几个问题:
缩短数据库恢复时间,重做日志中记录了的checkpoint的位置,这个点之前的页已经刷新回磁盘,只需要对checkpoint之后的重做日志进行恢复。这样就大大缩短了恢复时间。
缓冲池不够用时,根据LRU算法,溢出最近最少使用的页,如果页为脏页,强制执行checkpoint,将脏页刷新回磁盘。
重做日志不可用,是指重做日志的这部分不可以被覆盖,为什么?因为:由于重做日志的设计是循环使用的。这部分对应的数据还未刷新到磁盘上。数据库恢复时,如果不需要这部分日志,即可被覆盖;如果需要,必须强制执行checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。
checkpoint每次刷新多少页到磁盘?每次从哪里取脏页?什么时间触发checkpoint?
InnoDB存储引擎内部,两种checkpoint,分别为:
- Sharp Checkpoint
- Fuzzy Checkpoint
Sharp Checkpoint
Sharp Checkpoint发生在数据库关闭时,将所有的脏页都刷新回磁盘,这是默认的工作方式,即参数:innodb_fast_shutdown=1。
不适用于数据库运行时的刷新。Fuzzy Checkpoint
在数据库运行时,InnoDB存储引擎内部采用Fuzzy Checkpoint,只刷新一部分脏页。
几种发生Fuzzy Checkpoint的情况:
①MasterThread Checkpoint
异步刷新,每秒或每10秒从缓冲池脏页列表刷新一定比例的页回磁盘。异步刷新,即此时InnoDB存储引擎可以进行其他操作,用户查询线程不会受阻。
②FLUSH_LRU_LIST Checkpoint
InnoDB存储引擎需要保证LRU列表中差不多有100个空闲页可供使用。在InnoDB 1.1.x版本之前,用户查询线程会检查LRU列表是否有足够的空间操作。如果没有,根据LRU算法,溢出LRU列表尾端的页,如果这些页有脏页,需要进行checkpoint。因此叫:flush_lru_list checkpoint。
InnoDB 1.2.x开始,这个检查放在了单独的进程(Page Cleaner)中进行。好处:1.减少master Thread的压力 2.减轻用户线程阻塞。
设置参数:innodb_lru_scan_dept:控制LRU列表中可用页的数量,该值默认1024
③Async/Sync Flush Checkpoint
指重做日志不可用的情况,需要强制刷新页回磁盘,此时的页时脏页列表选取的。
这种情况是保证重做日志的可用性,说白了就是,重做日志中可以循环覆盖的部分空间太少了,换种说法,就是极短时间内产生了大量的redo log。
接下来会有几个变量,图解也不难,仔细看看。
InnoDB存储引擎,通过LSN(Log Sequence Number)来标记版本,LSN是8字节的数字。每个页有LSN,重做日志有LSN,checkpoint有LSN。
写入日志的LSN:redo_lsn
刷新回磁盘的最新页LSN:checkpoint_lsn
有如下定义:
checkpoint_age = redo_lsn - checkpoint_lsn
async_water_mark = 75% * total_redo_file_size
sync_water_mark = 90% * total_redo_file_size
刷新过程如下图所示:④Dirty Page too much Checkpoint
即脏页太多,强制checkpoint.保证缓冲池有足够可用的页。
参数设置:innodb_max_dirty_pages_pct = 75 表示:当缓冲池中脏页的数量占75%时,强制checkpoint。1.0.x之后默认75