MySQL 源代码分析

[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, […]


[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学习] MySQL/Innodb shutdown流程

写的比较简略////// ——————————— 我们通过mysqladmin来进行shutdown,会对mysqld发送SIGKILL信号,当接收到信号后,mysqld创建一个新的线程,线程调用函数为kill_server_thread 另外还可以通过调用COM_SHUTDOWN来关闭mysqld,没尝试过。当然最终调用的函数kill_server_thread函数。 kill_server_thread->kill_server 设置kill_in_progress=true,防止重复关闭mysqld,每个新的kill线程都要先判断这个值 设置abort_loop=1,这可以让某些循环等待的线程退出循环 调用close_connections a.关闭所有线程连接 b.关闭slave(end_slave()) 调用unireg_end->clean_up,主要做以下事情 a.清理一些全局结构体及内存资源 b.依次关闭插件plugin_shutdown 关闭innodb的入口函数是innobase_end,其关闭行为通过参数 innodb_fast_shutdown 来控制,流程如下: hash_table_free(innobase_open_tables)  //释放innodb表占用的内存 innobase_shutdown_for_mysql  //下面细述 释放一些全局内存和锁资源 innobase_shutdown_for_mysql流程: a.logs_empty_and_mark_files_at_shutdown 这个函数可以看做是innodb shutdown的主要函数。 |–>首先打印一条信息(InnoDB: Starting shutdown), 也就是我们在alter log里看到的,表示innodb shutdown开始 了。 srv_shutdown_state = SRV_SHUTDOWN_CLEANUP; |–>当 1.error monitor线程、lock_timeout线程(用于唤醒其他等待锁的线程)、以及monitor线程处于活跃状态时,loop 2.当前分配的事务数大于0(trx_n_mysql_transactions>0)或者存在状态不为Prepare的事务(UT_LIST_GET_LEN(trx_sys->trx_list) > trx_n_prepared), loop 3. 存在任意一个活跃后台线程时, loop 4.log_sys->n_pending_checkpoint_writes>0 或者log_sys->n_pending_writes大于0时,loop 5. 存在pending io时(buf_pool_check_no_pending_io) ,  loop |–>当UNIV_LOG_ARCHIVE被定义时,调用log_archive_all()写归档日志,log archive 貌似已被弃用,但代码还在。 |–>当srv_fast_shutdown=2时 log_buffer_flush_to_disk(); […]


[MySQL源码] Facebook MySQL Rev3814 通过动态可调的FSEG_FILLFACTOR减少空闲Page

背景: –每个索引有两个segment,用于管理叶子节点和非叶子节点 –每个segment包含多个extend,extend是一次文件扩展单元(64个Page) –每个Page默认非压缩表为16K 相关Tips: SPACE HEADER :表空间的第一个Page中,用于管理所有下述结构, FILE SEGMENT INODE用于描述SEGMENT信息 每个EXTEND都对应一个EXTEND DESCRIPTOR,描述信息单独记录在一个Page中,占用40个字节,一个Page可以存放256个描述符,因此每隔(256*64)个Page会有一个单独的EXTEND DESCRIPTOR PAGE SEGMENT INODE 用于管理其对应的SEGMENT信息,其中记录了该SEGMENT拥有的EXTEND的相关信息(以及该SEGMENT内的碎片Page信息) ,一个Page内可以存储的SEGMENT INODE个数为FSP_SEG_INODES_PER_PAGE(zip_size)。SEGMENT INODE的位置信息可以由BTREE的根节点中的SEGMENT HEADER得到。 问题: 每个SEGMENT通过一个宏FSEG_FILLFACTOR来决定什么时候进行段扩展,默认为8,也就是说,当一个SEGMENT中保留的Page数少于1/8时,就要对其进行扩展一个EXTEND,也就是64个Page。这个值是硬编码的,Facebook将其修改成可动态调整的值,根据其测试,可以减少不少空闲Page(达到10%) 上述逻辑的判断是在函数fseg_alloc_free_page_low中实现,当然比描述的更加复杂(一堆if else :-(..) 把Patch backport到了5.5.18,并跑了下test case,两个相同结构的表,load相同的数据。 innodb_segment_reserve_factor = 8   ibd文件大小为20971520 innodb_segment_reserve_factor = 500, ibd文件大小为23068672 大约减小了9% 有兴趣的同学可以从mysqlatfacebook在lauchpad上的Rev3814 来获得diff 更主要的是从学习该patch的过程中,顺便了解了一下fsp相关知识。   关于Innodb表空间,有两篇不错的文章,介绍的比较详细: http://blog.chinaunix.net/uid-26586115-id-3052168.html http://blog.chinaunix.net/uid-26586115-id-3061127.html 原创文章,转载请注明: 转载自Simple Life 本文链接地址: [MySQL源码] Facebook MySQL Rev3814 通过动态可调的FSEG_FILLFACTOR减少空闲Page Post Footer automatically generated by wp-posturl plugin for wordpress.


[MySQL源码] 一条简单insert语句的调用栈

以下仅用于本人调试MySQL所用,不具备可读性。 ———————————————————– CREATE TABLE t1 (a INT PRIMARY KEY, b INT NOT NULL) ENGINE=InnoDB; insert into t1 values (4,2); ha_innobase::write_row  |–>row_insert_for_mysql       |–>转换记录格式row_mysql_convert_row_to_innobase      |–>保存检查点savept = trx_savept_take(trx);      |–>row_ins_step         |–>加IX锁lock_table(0, node->table, LOCK_IX, thr)         |–> row_ins     //轮询索引,向表中插入记录,这里只有聚集索引         .   […]


[MySQL源码] Innodb如何处理auto_inc值

转载请署名:印风 ——————————————————– ha_innobase::write_row是向innodb写入记录的函数,进入函数时自增列的值还没被设置(如何是NULL的话),会调用handler::update_auto_increment来获取并更新自增列,然后再调用row_insert_for_mysql来实际插入记录。我们的精力主要集中在update_auto_increment及其调用的函数上。 先了解下handler的几个跟自增列相关的成员变量(根据注释及gdb推测):  a. ulonglong next_insert_id;   下一个插入自增列的值,当一次插入多行记录时(例如,insert select 操作),第一个没有指定自增列值的记录会从get_auto_increment函数获取的值并赋值给next_insert_id,这样对于剩下的记录就可以直接使用next_insert_id(每次都修改该值)。 b. ulonglong insert_id_for_cur_row;  当前记录的insert id 。第一次成功插入后,这个值被存储到THD::first_successful_insert_id_in_cur_stmt c. Discrete_interval auto_inc_interval_for_cur_row; 从get_auto_increment函数获取的insert id区间。 Discrete_interval 结构体又包含三个值 interval_min  区间的最小值 interval_values 区间内可用自增值的个数 interval_max 最大值 d.  uint auto_inc_intervals_count; 当前插入语句保留的自增区间数 e.  estimation_rows_to_insert 估计将要插入的记录数,在函数handler::ha_start_bulk_insert()中被设置,值为0表示未知。 ////////////////////////////////////////////////////////////////////////////////////////////////////// 下面来分析下handler::update_auto_increment主要流程 1.首先判断自增列是否已经赋值,或者是否不可以为NULL&&sql_mode为MODE_NO_AUTO_VALUE_ON_ZERO时,不做处理 if ((nr= table->next_number_field->val_int()) != 0 ||       (table->auto_increment_field_not_null &&       thd->variables.sql_mode & […]


[MySQL 源码] innodb如何创建二级索引

以下为分析问题时的随笔。写的很凌乱,仅做记录,以备后用。。。。。。 ////////////////////////////////////////////////////////////// ha_innobase::add_index是innodb创建索引的接口函数。 以下所有的讨论都是基于创建一个非聚集的二级索引。因此一些过程是被省略掉了。 1.获取数据词典信息           indexed_table = dict_table_get(prebuilt->table->name, FALSE); 2.检查索引键是否可用        error = innobase_check_index_keys(key_info, num_of_keys, prebuilt->table); 3.检查索引列长度 4. a.创建一个trx对象用于操作innodb数据词典,并创建新的数据词典信息 如果是主键,加LOCK_X,否则加LOCK_S锁 b.加数据词典锁row_mysql_lock_data_dictionary(trx); c.在ibdata的SYS_INDEXES中加载新的数据词典信息 d.trx_commit_for_mysql(trx); 提交刚刚创建的trx e.row_mysql_unlock_data_dictionary(trx) 以上步骤完成了对ibdata数据词典内的更新,在 完成后释放锁,这时候,如果在后续的row_merge_build_indexes时crash掉。trx_rollback_active不会drop掉新索引。 5. 调用函数row_merge_build_indexes实际创建索引,我们的讨论主要集中于此。 row_merge_build_indexes会读取表的聚集索引记录,创建临时表来保存这些记录,并使用合并排序算法进行排序以创建索引 a. 首先初始化merge file相关的数据结构,并初始化 merge_files = mem_alloc(n_indexes * sizeof *merge_files); block_size = 3 * sizeof *block; block = os_mem_alloc_large(&block_size); […]


MySQL 源代码分析

[MySQL源码] 在复制线程事务提交与更新relay-log.info之间crash导致的复制不一致

转载请署名:印风 ——————————————- 最近发现一种情况,在xid event和flush_relay_log_info中间crash,可能会导致数据不一致。 即事务提交了,但relay-log.info文件还没更新,这会造成重启crash recovery后事务被重复执行一次。 幸好,在innodb层记录了这些信息。并且Percona也提供了一个选项来利用这些信息。 1.相关全局变量 在trx/trx0sys.c文件中定义了如下变量 最后一个commit的事务的slave信息: trx_sys_mysql_master_log_name trx_sys_mysql_master_log_pos trx_sys_mysql_relay_log_name trx_sys_mysql_relay_log_pos 最后一个commit的binlog信息: trx_sys_mysql_bin_log_name trx_sys_mysql_bin_log_pos 2.写入信息 那么在什么时候会记录这些信息呢? 在函数mysql_bin_log_commit_pos中,确定写入innodb的binlog位置,backtrace如下 #0  mysql_bin_log_commit_pos #1  0x00000000007c88d4 in innobase_commit_ordered_low #2  0x00000000007cca97 in innobase_commit_ordered #3  0x000000000071efc5 in run_commit_ordered #4  MYSQL_BIN_LOG::trx_group_commit_leader #5  0x000000000071f49d in MYSQL_BIN_LOG::write_transaction_to_binlog_events #6  0x000000000071f681 in MYSQL_BIN_LOG::write_transaction_to_binlog #7  0x000000000071ff6b in binlog_flush_cache #8  binlog_commit_flush_trx_cache #9  MYSQL_BIN_LOG::log_and_order #10 0x000000000068a112 in […]