November 2012

[MySQL 源码] Innodb Pessimistic Insert流程

简单跟了下插入导致索引分裂的流程 ////////////////////////////////// 入口函数:row_ins_index_entry 实际上悲观插入和乐观插入是根据row_ins_index_entry_low的第一个参数来判断的 调用两次row_ins_index_entry_low 第一次参数为BTR_MODIFY_LEAF,表示只修改叶子节点,如果失败了 第二次参数为BTR_MODIFY_TREE,表示需要修改B-TREE,这时候会选择调用函数: btr_cur_pessimistic_insert: 1.首先做一次乐观插入(btr_cur_optimistic_insert),这实际上是重复的动作,会带来额外的开销,在后面的版本已经被移除了。http://bugs.mysql.com/bug.php?id=61456 2.检查锁,如果是聚集索引,还要记录undo,并记录回滚段指针,对于非聚集索引,要在Page上记录最大事务ID 之前已经分析过,这里不赘述。     err = btr_cur_ins_lock_and_undo(flags, cursor, entry,                     thr, mtr, &dummy_inh); 3.扩展文件,为Ibd预留足够的文件空间 n_extents = cursor->tree_height / 16 + 3; success = fsp_reserve_free_extents(&n_reserved, index->space,                            n_extents, FSP_NORMAL, mtr); 4.检查当前记录是否需要进行外部存储(page_zip_rec_needs_ext),如果需要的话,则需要对记录进行处理 在函数dtuple_convert_big_rec中,会去循环查找,能最大减少rec的外部存储列 但固定长度列、空列、外部存储列或则长度小于40字节的列不做考虑 另外,在是用DYNAMIC和COMPRESSED格式时,任何最大长度小于256字节的非BLOB列都是本地存储;而对于REDUNDANT和COMPACT类型而言,最大不超过788字节时都会本地存储。 big_rec_vec = dtuple_convert_big_rec(index, entry, &n_ext); 返回值类型为big_rec_struct,用于存储溢出数据。 如果找不到满足要求的最大列,返回NULL。 如果找到了,则替换原记录上的外部指针,并存储实际数据。 如果依然不满足页内存储,则继续寻找该记录上更多的列来进行外部存储。 5.开始进行索引分裂: a.如果当前记录的cursor在根Page上,则分裂节点,提升BTREE高度,然后再插入记录 *rec = btr_root_raise_and_insert(cursor, […]

quicklz 接口函数

翻译自quicklz c手册 /////////////////////////////////////////// 压缩函数: size_t qlz_compress( const void *source,  char *destination,  size_t size,  qlz_state_compress *state_compress) source,源字符地址 destination,压缩后的数据存储起始地址,其大小至少为size+400字节 size,需要压缩的源字符串长度,必须在1到2^32-1之间 qlz_state_compress在quicklz.h中定义,用于压缩算法过程中的临时状态存储 注意qlz_state_compress结构体非常庞大,因此不应该在函数局部定义,以防止产生栈溢出。 解压函数: size_t qlz_decompress( const char *source,  void *destination,  qlz_state_decompress *state_decompress) 解压source并将解压结果写入到destination中。 size_t qlz_size_compressed(const char *source) 获取压缩数据的大小, int qlz_get_setting(int settings) 用于获取quiklz的设置信息,根据传参来决定 0 QLZ_COMPRESSION_LEVEL 1 sizeof(qlz_state_compress) 2 sizeof(qlz_state_decompress) 3 QLZ_STREAMING_BUFFER 6 1 if QLZ_MEMOMRY_SAFE is defined, otherwise 0 7 […]

[MySQL 学习] zlib库相关结构和函数

以下主要是阅读zlib库时,对库函数的注释的翻译,也是为了帮助理解zlib在innodb压缩表中的应用 这里只考虑了Innodb用到的函数,其他的具体参考zlib.h文件,里面的注释写的非常详细 ————————- 1.主要用到的结构体是z_stream,定义在文件zlib/zlib.h中,我们需要去定义的字段包括 Bytef    *next_in 输入的源字符串 uInt     avail_in 输入源字符串长度,当avail_in下降到0时,必须更新next_in和avail_in Bytef    *next_out 输出字符串 uInt     avail_out 在next_out中的可用空闲空间,当avail_out下降到0时,必须更新next_out alloc_func zalloc 内存分配函数,Innodb里对应的函数指针是page_zip_zalloc 如不指定需要设置为NULL free_func  zfree 内存释放函数,Innodb里对应函数指针为page_zip_free 如不指定需要设置为NULL voidpf     opaque 会被作为参数传递给zalloc和zfree,在innodb里使用的是mem_heap. 2.压缩函数(只涉及到Innodb中的调用) a.deflateInit2(strm, level, method, windowBits, memLevel, strategy) 参数 @1, z_stream对象 @2,压缩级别 @3,值为Z_DEFLATED,当前唯一的defalte压缩方法,用于以后扩展 @4,窗口比特数,范围在8~15,更大的值意味着消耗更多的内存来获得更好的压缩效果,如果使用deflateInit来初始化的话默认值为15 windowBits也可以在-8~-15间赋值,用于raw deflate(不理解?),这时候使用-windowsBits来决定窗口大小,deflate()会生成raw deflate data,没有zlib头和尾,并且也不会去调用adler32 当使用gzlib编码时,windowsbits也可以设置为大于15,但跟zlib的文件格式会有很大的不同。 在Innodb中值为UNIV_PAGE_SIZE_SHIFT,值为14(如果Page Size为默认16K的话) @5,memlevel用于指定分配多少内存用于内部的压缩状态,值为1将使用最小的内存但更慢并降低压缩比;memlevel值为9时,会使用最大内存来获得最快的速度。默认值为8,在Innodb里使用的值为MAX_MEM_LEVEL,deflate需要的内存为: […]

[MySQL 源码] 从buffer pool中获取空闲block流程

当我们将一个page读入内存时,需要先为其分配一个block,从buffer pool中获取。入口函数为buf_LRU_get_free_block 之前在http://mysqllover.com/?p=303有简要介绍,这里详细看看,当然,跟最近博客的主题一样,我们还是主要针对压缩表来分析。 以下分析基于Percona Server 5.5.18 buf_LRU_get_free_block loop: 1.block = buf_LRU_get_free_only(buf_pool) 首先从buf_pool->free链表尾部读取,如果有空闲页,则将其从buf_pool->free中移除,设置bpage->state=BUF_BLOCK_READY_FOR_USE,然后返回 上述流程需要加buf_pool->free_list_mutex锁 2.如果1获得了一个block,还需要重置压缩页描述符block->page.zip为0,然后直接返回 3.如果在buf_pool->free上没有block,则从buf_pool->LRU或unzip_LRU的尾部开始扫描,尝试找一个空闲block. freed = buf_LRU_search_and_free_block(buf_pool, n_iterations); //n_iterations是一个计数器,表示尝试释放但失败的次数。 A.持有buf_pool->LRU_list_mutex锁 B.首先,尝试从buf_pool->unzip_LRU上释放block,这种情况下不会释放压缩页数据 freed = buf_LRU_free_from_unzip_LRU_list(buf_pool, n_iterations, have_LRU_mutex); >>判断是否从unzip_LRU上驱逐block 理论上讲,从buf_pool->unzip_LRU上应该更容易获得一个block,因为我们可以选择一个脏块(只驱逐解压页),但当我们尝试5次还是没有找到时,则直接返回到正常的驱逐block的逻辑,即从LRU上获取 另外也有函数buf_LRU_evict_from_unzip_LRU用来判断是否从unzip_LRU上驱逐block,其判断逻辑如下: >>>需要持有buf_pool->LRU_list_mutex >>>如果buf_pool->unzip_LRU长度为0 ,返回FALSE >>>如果buf_pool->unzip_LRU小于buf_pool->LRU的十分之一,返回FALSE >>>如果buf_pool->freed_page_clock == 0,表示之前没有进行过任何block驱逐,默认假设工作负载为disk bound,返回TRUE freed_page_clock是一个序列号,用来计数从LRU尾部移除的block数,可以不加锁读取该变量 >>>计算最近的平均IO量     io_avg = buf_LRU_stat_sum.io / BUF_LRU_STAT_N_INTERVAL         + buf_LRU_stat_cur.io; 最近50秒的平均IO+当前的IO,获得最近的平均IO负载   […]

[MySQL 源码] MySQL drop table(压缩表)效率与流程分析

 之前发生过一起连续drop压缩表,最后长时间等待信号量crash,线上alert log里的报错是: OS WAIT ARRAY INFO: reservation count 36647199, signal count 34050225 –Thread 1331538240 has waited at row0purge.c line 680 for 950.00 seconds the semaphore: S-lock on RW-latch at 0xe60b60 ‘&dict_operation_lock’ a writer (thread id 1383319872) has reserved it in mode exclusive number of readers 0, waiters flag 1, lock_word: 0 Last time read locked […]

[MySQL学习] Innodb崩溃恢复流程

简要记录跟踪代码,很多代码流程没有细细的跟进去,只是了解了个大概,杂七杂八,还有太多不了解的地方。 不过,一知半解总比一无所知要好点…sign… //////////////////////////////////////////// 一、innobase_init 1.初始化存储引擎接口函数、检查指定的page大小(innodb_page_size,Percona版本支持16k以下的page size定义)、innodb_log_block_size。 2.检查是否通过记录在innodb层的relay log信息更新relay-info文件(通过innodb_recovery_update_relay_log来控制) 3.innodb文件路径、类型、各类全局变量等信息初始化, 4.innobase_start_or_create_for_mysql //主要函数,稍后细述 5.更新每个bp实例的buf_pool->LRU_old_ratio(innodb_old_blocks_pct),也就是LRU上old list的百分比。 6.初始化innobase_share_mutex、prepare_commit_mutex、commit_threads_m、commit_cond_m、commit_cond等变量。 可以看到在这个函数中除了innobase_start_or_create_for_mysql这个主要函数外,基本上都是些变量之类的初始化,我们简要看看innobase_start_or_create_for_mysql主要干了什么吧。 二、innobase_start_or_create_for_mysql 1. 当buffer pool size大于等于1000MB时,在innodb层最大允许等待同一信号量的线程数srv_max_n_threads=5000 当1000MB >= buffer pool size >= 8MB,只使用1个bp实例,srv_max_n_threads = 10000 当buffer pool size小于8MB时,只使用1个bp实例,srv_max_n_threads = 1000 2.调用srv_boot srv_normalize_init_values(void) //初始化全局变量 srv_general_init(void) //Initialize synchronization primitives, memory management, and thread local storag srv_init(); 初始化各种全局信号量,kernel_mutex、srv_sys以及srv_sys->threads数组中每个slot的event(调用os_event_create初始化),同样的还需要初始化srv_mysql_table数组(类型为srv_slot_t) dict_ind_init();//初始化dict_ind_redundant和dict_ind_compact,为infimum 和supremum 记录 创建一个名为SYS_DUMMY1/SYS_DUMMY2的表结构,暂时不了解用途。 初始化srv_conc_slots 3.创建临时文件srv_dict_tmpfile和srv_misc_tmpfile,调用函数os_file_create_tmpfile()来创建   […]

MySQL5.6 RC innodb_log_compressed_pages 测试 及实现简述

MySQL5.6 RC增加了一个参数innodb_log_compressed_pages,以判定在压缩page时,是否在redo中存储压缩页数据。 最近把该特性在5.5.18上做了实现,从测试的效果来看,大大减少了redo log的写入量(降低到1/3~1/5),这会降低checkpoint的概率,并稍微增加了tps 以下数据,包括tps及每秒的log平均写入量(采集自status值Innodb_os_log_written) innodb_log_compressed_pages =0 innodb_log_compressed_pages =1 Prepare data  8333 rows/s     2.37MB/s 8130 rows/s 6.79MB/s update_non_index.lua 1880.32 per sec  3.26MB/s 1780.51 per sec 16.56MB/s update_index.lua 1896.34 per sec 3.19MB/s 1824.21 per sec 16.93MB/s oltp.lua 576.85 per sec 1.62MB/s 559.52 per sec 14.27MB/s 后续还需要做一些相关的crash恢复测试 //////////////////////////////////////////////////////////////// 其他:innodb_log_compressed_pages主要修改细节 原本innodb记录每个压缩Page到redo中,目的是为了防止在崩溃恢复时,压缩的算法发生改变,尤其在引入了可调整的压缩级别(innodb_compression_level)后,如果重启后,使用不同的level,就可能无法恢复出一致的压缩页 mlog的背景知识可以参考这个: http://www.mysqlops.com/2012/04/06/innodb-log2.html 目前对mini-transaction、redo以及崩溃恢复机制还不是特别了解,以下只是简述,并未深入。 1.btr_page_reorganize_low @增加参数 compression_level @在做page reorgnize之前,需要先写入一条mlog […]