概述
IO_CACHE为mysql读写文件的模块,在mysql操作文件时,通过该模块与文件进行交互。该模块类似于mysql的文件缓存,通过对文件的整读、整写来提高mysql的io效率。读写binlog、读写relay log等文件操作均是通过该模块进行。
IO_CACHE
基本结构
IO_CACHE的基础结构体如下
typedef struct st_io_cache
{
uchar *buffer; /*文件中读取的文件内容*/
my_off_t pos_in_file; /*buffer在整个文件中的偏移量*/
uchar *read_pos; /*读取到的当前位置*/
uchar *read_end; /*最大允许读取的位置*/
size_t buffer_length; /*申请的buffer大小*/
uchar *write_buffer; /*用在WRITE 或者 SEQ_READ_APPEND中的写缓存*/
uchar *write_pos; /*指向写缓存中的位置*/
uchar *write_end; /*最大允许写入位置*/
my_off_t end_of_file;
uchar *append_read_pos; /*SEQ_READ_APPEND使用,用作在写缓存中读取到的位置*/
File file /*对应文件的文件描述符*/
enum cache_type type; /*缓存类型,包括读、写、顺序读、FIFO读、网络读、网络写*/
int seek_not_done /*标志在执行读写操作之前,是否需要执行seek*/
ulong disk_writes; /*缓存刷入磁盘的次数*/
}IO_CACHE
其中cache_type决定io_cache的使用方法,cache_type包括 TYPE_NOT_SET、READ_CACHE、WRITE_CACHE、SEQ_READ_APPEND、READ_FIFO、READ_NET、WRITE_NET,这里主要对READ_CACHE模式、WRITE_CACHE模式和SEQ_READ_APPEND模式做主要说明。
READ_CACHE模式
read_cache模式为只读模式,每次读取文件中的一块内容,存放到缓存中。当发生读文件请求时,首先从缓存中读取文件内容。这种读文件的方式可以间接的将读文件合并,达到减少IO操作的目的,提升IO性能。
在读取当前不会被修改的文件时,使用这种模式来读取文件,dump线程、SQL线程在读取非正在使用的文件时,均使用这种模式的IO_CACHE。
初始化
使用函数init_io_cache_ext来对IO_CACHE结构体进行初始化。输入参数cache_type为READ_CACHE。
int init_io_cache_ext(IO_CACHE *info, File file, size_t cachesize, ...cache_myflags...
| min_cache=use_async_io ? IO_SIZE*4 : IO_SIZE*2; /*最小单位为IO_SIZE的倍数*/
| if (!(cache_myflags & MY_DONT_CHECK_FILESIZE)) /*文件大小不会再增长了*/
end_of_file= mysql_file_seek(file, 0L, MY_SEEK_END, MYF(0)); /*计算文件大小*/
| cachesize= ((cachesize + min_cache-1) & ~(min_cache-1)); /*对cachesize以最小单位min_cache做对齐*/
| for (;;)
if (cachesize < min_cache)
cachesize = min_cache; /*最小的cachesize为min_cache*/
info->buffer= (uchar*) my_malloc(......,buffer_block, flags)
/*如果malloc失败,缩小cachesize到原大小的3/4,知道申请成功*/
cachesize= (cachesize*3/4 & ~(min_cache-1));
| info->read_length=info->buffer_length=cachesize;
| info->request_pos= info->read_pos = info->buffer
| info->read_end=info->buffer /*end 等于 buffer 表示buffer里面没有任何内容*/
| init_functions(info); /*初始化读取函数*/
info->read_function = _my_b_read
在READ_CACHE模式下,初始化后缓存中是没有内容的。
读取文件
使用宏my_b_read来读取
#define my_b_read(info,Buffer,Count)
((info)->read_pos + (Count) <= (info)->read_end ?
/*如果缓存buffer中有足够的长度,直接从buffer中读取*/
(memcpy(Buffer,(info)->read_pos,(size_t) (Count)), ((info)->read_pos+=(Count)),0) :
/*如果缓存buffer中没有足够的长度(或者刚初始化),使用读取函数读取*/
(*(info)->read_function)((info),Buffer,Count))
读取函数首先将缓存中内容全部读出,然后从文件中以IO_SIZE为单位读取到足够的内容,剩余的内容读取到缓存的buffer中之后,再读取出。
int _my_b_read(IO_CACHE *info, uchar *Buffer, size_t Count)
/*将IO_CACHE中剩余的byte copy到buffer中*/
| left_length= (size_t) (info->read_end-info->read_pos)
| memcpy(Buffer,info->read_pos, left_length);
/*计算文件的偏移量,保证读取的文件内容是以IO_SIZE为单位*/
| diff_length= (size_t) (pos_in_file & (IO_SIZE-1));
/*如果需要读取的文件内容超出2个IO_SIZE*/
/*直接从文件中读取内容到buffer中,读取IO_SIZE * 2的倍数,剩余的仍然从缓存中读取*/
| if (Count >= (size_t) (IO_SIZE+(IO_SIZE-diff_length)))
| mysql_file_read(info->file,Buffer, length, info->myflags)
/*再次读取一个IO_CACHE_SIZE的文件内容到IO_CACHE中*/
| max_length= info->read_length-diff_length;
| mysql_file_read(info->file,info->buffer, max_length,......
/*更新变量read_pos、read_end、pos_in_file的值*/
| info->read_pos=info->buffer+Count;
| info->read_end=info->buffer+length;
| info->pos_in_file=pos_in_file;
/*将剩余的内容copy到buffer中*/
| memcpy(Buffer, info->buffer, Count);
需要注意的是,每次读取均是以IO_CACHE为单位,如果读取的起始位置不为文件头,则通过diff_length进行对齐。
info->buffer读取到的buffer的起始位置,info->read_pos为读取到的当前,info->read_end为buffer的结束位置。info->pos_in_file为当前读取的buffer在文件中的位置,下次读取文件的起始位置。
通过info->read_end - info->read_pos计算buffer中剩余的数据长度。
WRITE_CACHE模式
write_cache为写入模式,IO_CACHE申请一块buffer作为写入缓存,将需要写入文件的内容先写入到缓存buffer中,然后统一写入到文件中,间接达到合并IO操作的目的,提高IO的性能。
在需要写入文件时,使用这种模式来操作文件,binlog的默认打开方式为WRITE_CACHE。
初始化
使用函数init_io_cache_ext来对IO_CACHE结构体进行初始化。输入参数cache_type为WRITE_CACHE。
初始化大体流程同READ_CACHE模式相同,需要注意的代码如下
int init_io_cache_ext(IO_CACHE *info, File file, size_t cachesize, ...cache_myflags...
| info->pos_in_file= seek_offset; /*写入的起始点*/
| info->write_pos = info->buffer; /*写入的起始位置,表示没有任何信息写入*/
| info->write_end = info->buffer+info->buffer_length- (seek_offset & (IO_SIZE-1));
| init_functions /*初始化读取函数*/
info->write_function = _my_b_write;
写入文件
使用宏my_b_write来读取
#define my_b_write(info,Buffer,Count)
((info)->write_pos + (Count) <=(info)->write_end ?
/*如果剩余空间足够写入的空间,使用当前buffer做缓存*/
(memcpy((info)->write_pos, (Buffer), (size_t)(Count)), ((info)->write_pos+=(Count)),0) :
/*如果剩余空不足,则调用函数进行写入*/
(*(info)->write_function)((info),(uchar *)(Buffer),(Count)))
写入函数,首先将需要写入的内容全部写入剩余的缓存中,将缓存中的内容全部写入文件,
_my_b_write(IO_CACHE *info, const uchar *Buffer, size_t Count)
/*将部分buffer中的内容写入缓存,将缓存中的内容刷新到文件中*/
| memcpy(info->write_pos,Buffer,(size_t) (info->write_end - info->write_pos));
| my_b_flush_io_cache(info,1) /*将缓存中的内容全部刷入文件*/
/*如果剩余的长度大于IO_SIZE,以IO_SIZE为单位,将所有大于IO_SIZE的内容直接写入文件*/
| if (Count >= IO_SIZE)
| length=Count & (size_t) ~(IO_SIZE-1); /*以IO_SIZE对齐*/
| mysql_file_write(info->file, Buffer, length, info->myflags | MY_NABP)
| info->pos_in_file+=length;
/*将剩余的buffer内容copy到IO_CACHE中*/
| memcpy(info->write_pos,Buffer,(size_t) Count);
| info->write_pos+=Count;
刷新缓存进入文件
my_b_flush_io_cache
| length=(size_t) (info->write_pos - info->write_buffer) /*计算缓存中的buffer大小*/
| if (!append_cache && info->seek_not_done)
if (mysql_file_seek(info->file, pos_in_file, MY_SEEK_SET, MYF(0)) ==
MY_FILEPOS_ERROR) /*定位文件中的位置*/
| info->pos_in_file+=length
/*保证整个缓存以IO_SIZE对齐,即如果写满,一定为IO_SIZE的整数倍*/
| info->write_end= (info->write_buffer+info->buffer_length- ((pos_in_file+length) & (IO_SIZE-1)));
| mysql_file_write(info->file,info->write_buffer,length, info->myflags | MY_NABP) /*写入文件*/
| set_if_bigger(info->end_of_file,(pos_in_file+length));
| info->write_pos=info->write_buffer; /*重置info->write_pos*/
SEQ_READ_APPEND模式
SEQ_READ_APPEND模式为一种读写共用模式,对同一个文件,同时申请两块buffer用作文件缓存,一块用作读文件的buffer,另一块用作写文件的buffer。当读取到文件位置与写入文件的位置发生重合时,可以直接在写缓存中直接读取,减少读操作读取文件的io操作,以提高IO效率。
IO线程在写入relay log时使用这种模式来打开文件,如果SQL线程与IO线程读取相同的文件时,IO线程和SQL线程使用相同的IO_CACHE。
初始化
使用函数init_io_cache_ext来对IO_CACHE结构体进行初始化。输入参数cache_type为SEQ_READ_APPEND。
初始化大体流程同READ_CACHE模式相同,但是需要申请两块buffer分别用于读写。
int init_io_cache_ext(IO_CACHE *info, File file, size_t cachesize, ...cache_myflags...
| if (type == SEQ_READ_APPEND)
buffer_block *= 2; /*将bufer_size 扩大到双倍*/
| info->buffer= (uchar*) my_malloc(......,buffer_block, flags)
| if (type == SEQ_READ_APPEND)
/*info->buffer和info->write_buffer分别指向两块buffer的起始位置*/
info->write_buffer = info->buffer + cachesize;
| if (type == SEQ_READ_APPEND)
info->append_read_pos = info->write_pos = info->write_buffer; /*append_read_pos为读取时使用*/
info->write_end = info->write_buffer + info->buffer_length;
mysql_mutex_init(key_IO_CACHE_append_buffer_lock,...... /*读取和写入同一块buffer使用的锁*/
| init_functions
info->read_function = _my_b_seq_read;
info->write_function = 0; /*不使用*/
写入文件
不使用宏来写入文件,使用单独的append函数来写入,所有的写入均append到文件的结尾。buffer的使用同WRITE_CACHE模式下的写入是相同的,在buffer充足的情况下,首先写入buffer,如果buffer空间不足的情况下,先将buffer中的内容append到文件结尾,再继续使用buffer。
int my_b_append(IO_CACHE *info, const uchar *Buffer, size_t Count)
| lock_append_buffer(info);
| rest_length= (size_t) (info->write_end - info->write_pos); /*计算buffer剩余空间*/
/*如果剩余空间充足*/
| memcpy(info->write_pos,Buffer,(size_t) Count); /*直接写入缓存buffer中*/
| info->write_pos+=Count;
/*如果剩余空间不足*/
| memcpy(info->write_pos, Buffer, rest_length); /*先将剩余的内容先写入到buffer中*/
| length=Count & (size_t) ~(IO_SIZE-1); /*以IO_SIZE对齐*/
| my_b_flush_io_cache(info,0) /*将缓存中的内容append到文件中,重置写buffer*/
| memcpy(info->write_pos,Buffer,(size_t) Count); /*将对齐后剩余的内容写入新的buffer*/
| info->write_pos+=Count;
| unlock_append_buffer(info);
读取文件
使用宏my_b_read来读取,如果buffer不足使用读取函数读取,读取函数与READ_CACHE模式使用的读取函数不同。
_my_b_seq_read
| if ((left_length=(size_t) (info->read_end-info->read_pos)))
DBUG_ASSERT(Count > left_length); 读取的长度一定大于read_buffer中剩余的
| lock_append_buffer(info);
| if ((pos_in_file=info->pos_in_file + (size_t) (info->read_end - info->buffer)) >= info->end_of_file) /*判断是否读取到了尾端,开始读取write_buffer*/
| goto read_append_buffer;
没有读取到write_buffer
| if (mysql_file_seek(info->file, pos_in_file, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR)
| 正常读取,与READ_CACHE模式的读取方式相同
读取到write_buffer
/*初始时info->append_read_pos 等于info->write_pos, 写入后info->write_pos会增加 */
| len_in_buff = (size_t) (info->write_pos - info->append_read_pos);
| copy_len= MY_MIN(Count, len_in_buff); /*取较小的copy到buffer中*/
/*将需要读取长度copy到buffer中*/
| memcpy(Buffer, info->append_read_pos, copy_len);
| info->append_read_pos += copy_len;
/*将write_buffer中剩余的字节读取到read_buffer中*/
| memcpy(info->buffer, info->append_read_pos,
(size_t) (transfer_len=len_in_buff - copy_len)); /*将剩余的字节copy到read buffer中*/
| info->read_pos= info->buffer;
info->read_end= info->buffer+transfer_len; /*剩余的所有字节作为 read_end*/
info->append_read_pos=info->write_pos; /*置append_read_pos到write_pos*/
info->pos_in_file=pos_in_file+copy_len; /* info->buffer在文件中的起始位置*/
info->end_of_file+=len_in_buff; /*可读的最大长度*/
info->read_pos、info->read_end、info->buffer的使用与READ_CACHE模式下使用的相同。
info->write_pos、info->write_end、info->write_buffer的使用与WRITE_CACHE模式下的使用的相同。
info->append_read_pos为读取IO_CACHE时,读取到了write_buffer(文件末尾)时使用的变量,通过info->write_pos - info-> append_read_pos来计算可以从write_buffer中读取到的字节长度。
总结
IO_CACHE模块是mysql用来提升IO性能的模块,对于长度小于IO_SIZE(默认为4096字节)的文件操作(读取或者写入),该模块可以达到合并小的IO操作,提升IO性能的目的。如果每次读取或者写入文件的长度如果超过IO_SIZE * 2,那么每次IO_CACHE模块会直接操作文件,并不会达到提升IO性能的目的。
另外需要注意的是SEQ_READ_APPEND模式,该模式为IO线程和SQL线程操作同一文件时来提升性能的模式,在5.7以及5.7版本前均是使用该模式来打开IO线程和SQL线程共同使用的relay log。但是由于锁的原因,在mysql-8版本中,IO线程和SQL线程分别使用独自的IO_CACHE,不再使用同一个IO_CACHE的SEQ_READ_APPEND模式。