源码浅析:MySQL一条insert操做,会写哪些文件?包括UNDO相关的文件吗?

DML操做的大体流程node

在解答上述疑惑以前,咱们来梳理一下DML操做的大体流程:mysql

一、语法解析、语义解析sql

二、生成执行计划数据结构

三、事务修改阶段函数

  1) 激活事务,事务状态由not_active变为active工具

  2) 查找定位数据ui

  3) 乐观插入this

  4) 记录insert相关的undo记录,并将undo记录的变化写入redo log bufferspa

  5) 进行insert 元组插入,及实际的插入操做,并写入到redo log buffer指针

  6) binlog event 写入到 binlog cache

四、事务提交阶段

  1) 事务prepare

  2) redo组提交,redo落盘

  3) flush binlog cache到binlog文件,而后fsync binlog文件将它落盘

  4) innodb进行提交,事务状态由prepare变为not_active

 

写了哪些文件?会写UNDO相关的文件吗?

从上述流程中能够看到,主要对redo log file和binlog进行了写入。

那么是否会实时地写入Undo tablespace呢?

咱们先来简单地分析一下:

1.磁盘中的undo segment,不论它是保存在system tablespace中,仍是保存在独立的undo tablespace中,根据页的物理结构(参考阿里内核月报)来看,它们是离散地分布在表空间文件中的。所以须要读/写的时候,会产生不少的随机读写io操做,而随机读写的效率是很是低的;

2.Innodb使用了不少种方法来将磁盘随机读写尽量地转换成顺序读写,好比change buffer特性、WAL特性、MRR、extent块管理,等等。上述这些都是在尽量地减小磁盘随机读写。因此Innodb应该不会将undo日志实时地落盘;

3.在上述流程中的3.4部分,已经将Undo的变化写入到redo log buffer了,redo会在事务提交时落盘,因此即便在事务失败、Undo没有落盘的状况下实例宕机,从新启动实例的时候,也会从redo中找到Undo来回滚,从而保证事务的原子性。

综上,能够初步判断Undo不会实时地落盘。可是这只是根据原理来进行分析的,为了肯定个人分析是否正确,能够打开源码进行分析验证,或使用strace等工具来验证。

 

如下是源码浅析:

插入的流程:

 1     //trx_undof_page_add_undo_rec_log--记录undo的redo log 入redo buffer
 2 >    mysqld.exe!trx_undof_page_add_undo_rec_log(unsigned char * undo_page, unsigned __int64 old_free, unsigned __int64 new_free, mtr_t * mtr) 行 74
 3      mysqld.exe!trx_undo_page_set_next_prev_and_add(unsigned char * undo_page, unsigned char * ptr, mtr_t * mtr) 行 204
 4      //trx_undo_page_report_insert--记录insert的undo记录
 5     mysqld.exe!trx_undo_page_report_insert(unsigned char * undo_page, trx_t * trx, dict_index_t * index, const dtuple_t * clust_entry, mtr_t * mtr) 行 537
 6      mysqld.exe!trx_undo_report_row_operation(unsigned __int64 flags, unsigned __int64 op_type, que_thr_t * thr, dict_index_t * index, const dtuple_t * clust_entry, const upd_t * update, unsigned __int64 cmpl_info, const unsigned char * rec, const unsigned __int64 * offsets, unsigned __int64 * roll_ptr) 行 1951
 7      mysqld.exe!btr_cur_ins_lock_and_undo(unsigned __int64 flags, btr_cur_t * cursor, dtuple_t * entry, que_thr_t * thr, mtr_t * mtr, unsigned __int64 * inherit) 行 2984
 8      //btr_cur_optimistic_insert--进行乐观插入
 9     mysqld.exe!btr_cur_optimistic_insert(unsigned __int64 flags, btr_cur_t * cursor, unsigned __int64 * * offsets, mem_block_info_t * * heap, dtuple_t * entry, unsigned char * * rec, big_rec_t * * big_rec, unsigned __int64 n_ext, que_thr_t * thr, mtr_t * mtr) 行 3244
10      mysqld.exe!row_ins_clust_index_entry_low(unsigned __int64 flags, unsigned __int64 mode, dict_index_t * index, unsigned __int64 n_uniq, dtuple_t * entry, unsigned __int64 n_ext, que_thr_t * thr, bool dup_chk_only) 行 2447
11      mysqld.exe!row_ins_clust_index_entry(dict_index_t * index, dtuple_t * entry, que_thr_t * thr, unsigned __int64 n_ext, bool dup_chk_only) 行 3162
12      mysqld.exe!row_ins_index_entry(dict_index_t * index, dtuple_t * entry, que_thr_t * thr) 行 3292
13      mysqld.exe!row_ins_index_entry_step(ins_node_t * node, que_thr_t * thr) 行 3442
14      mysqld.exe!row_ins(ins_node_t * node, que_thr_t * thr) 行 3584
15      mysqld.exe!row_ins_step(que_thr_t * thr) 行 3769
16      mysqld.exe!row_insert_for_mysql_using_ins_graph(const unsigned char * mysql_rec, row_prebuilt_t * prebuilt) 行 1734
17      mysqld.exe!row_insert_for_mysql(const unsigned char * mysql_rec, row_prebuilt_t * prebuilt) 行 1853
18      mysqld.exe!ha_innobase::write_row(unsigned char * record) 行 7484
19      mysqld.exe!handler::ha_write_row(unsigned char * buf) 行 7845
20      mysqld.exe!write_record(THD * thd, TABLE * table, COPY_INFO * info, COPY_INFO * update) 行 1860
21      mysqld.exe!Sql_cmd_insert::mysql_insert(THD * thd, TABLE_LIST * table_list) 行 780
22      mysqld.exe!Sql_cmd_insert::execute(THD * thd) 行 3092
23      mysqld.exe!mysql_execute_command(THD * thd, bool first_level) 行 3520
24      mysqld.exe!mysql_parse(THD * thd, Parser_state * parser_state) 行 5519
25      mysqld.exe!dispatch_command(THD * thd, const COM_DATA * com_data, enum_server_command command) 行 1432
26      mysqld.exe!do_command(THD * thd) 行 997
27      mysqld.exe!handle_connection(void * arg) 行 301
28      mysqld.exe!pfs_spawn_thread(void * arg) 行 2190
29      mysqld.exe!win_thread_start(void * p) 行 37

 

其中,trx_undo_page_report_insert函数的代码以下:

 1 /**********************************************************************//**
 2 在UNDO日志中报告汇集索引记录的插入。注意:这里的UNDO日志,指的是内存中的数据结构
 3 @return在页面上插入的条目的偏移量(若是成功),若是失败则为0 */
 4 static
 5 ulint
 6 trx_undo_page_report_insert(
 7 /*========================*/
 8     page_t*        undo_page,    /*!< in: undo log page */
 9     trx_t*        trx,        /*!< in: transaction */
10     dict_index_t*    index,        /*!< in: clustered index */
11     const dtuple_t*    clust_entry,    /*!< in: index entry which will be
12                     inserted to the clustered index */
13     mtr_t*        mtr)        /*!< in: mtr */
14 {
15     ulint        first_free;
16     byte*        ptr;
17     ulint        i;
18 
19     //...省略若干内容
20 
21 
22     /* 预留2字节给指向下一条UNDO日志的指针 */
23     ptr += 2;
24 
25     /* Store first some general parameters to the undo log */
26     *ptr++ = TRX_UNDO_INSERT_REC;
27     ptr += mach_u64_write_much_compressed(ptr, trx->undo_no);
28     ptr += mach_u64_write_much_compressed(ptr, index->table->id);
29     /*----------------------------------------*/
30     /* 而后存储惟一肯定要在聚簇索引中插入的记录所需的字段 */
31 
32     for (i = 0; i < dict_index_get_n_unique(index); i++) {
33 
34         const dfield_t*    field    = dtuple_get_nth_field(clust_entry, i);
35         ulint        flen    = dfield_get_len(field);
36 
37         if (trx_undo_left(undo_page, ptr) < 5) {
38 
39             return(0);
40         }
41 
42         ptr += mach_write_compressed(ptr, flen);
43 
44         if (flen != UNIV_SQL_NULL) {
45             if (trx_undo_left(undo_page, ptr) < flen) {
46 
47                 return(0);
48             }
49 
50             ut_memcpy(ptr, dfield_get_data(field), flen);
51             ptr += flen;
52         }
53     }
54 
55     if (index->table->n_v_cols) {
56         if (!trx_undo_report_insert_virtual(
57             undo_page, index->table, clust_entry, &ptr)) {
58             return(0);
59         }
60     }
61     /* 调用trx_undo_page_set_next_prev_and_add函数 */
62     return(trx_undo_page_set_next_prev_and_add(undo_page, ptr, mtr));
63 }

 trx_undo_page_set_next_prev_and_add函数的代码以下:

 1 /**********************************************************************//**
 2 在UNDO page中为写入到ptr的撤消记录设置下一个和上一个指针。 经过为此UNDO日志写入的字节数更新第一个空闲值。
 3 @return在页面上插入的条目的偏移量(若是成功),若是失败则为0 */
 4 static
 5 ulint
 6 trx_undo_page_set_next_prev_and_add(
 7 /*================================*/
 8     page_t*        undo_page,    /*!< in/out: undo log page */
 9     byte*        ptr,        /*!< in: ptr up to where data has been
10                     written on this undo page. */
11     mtr_t*        mtr)        /*!< in: mtr */
12 {
13     ulint        first_free;    /*!< offset within undo_page */
14     ulint        end_of_rec;    /*!< offset within undo_page */
15     byte*        ptr_to_first_free;
16                     /* pointer within undo_page
17                     that points to the next free
18                     offset value within undo_page.*/
19 
20     //...省略若干代码
21 
22     ptr_to_first_free = undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE;
23 
24     first_free = mach_read_from_2(ptr_to_first_free);
25 
26     /* 写入上一个UNDO日志记录的偏移量 */
27     mach_write_to_2(ptr, first_free);
28     ptr += 2;
29 
30     end_of_rec = ptr - undo_page;
31 
32     /* 写入下一个UNDO日志记录的偏移量 */
33     mach_write_to_2(undo_page + first_free, end_of_rec);
34 
35     /* 将偏移量更新为第一个空闲的UNDO记录 */
36     mach_write_to_2(ptr_to_first_free, end_of_rec);
37 
38     /* 将此日志条目写入UNDO日志,注释原文是Write this log entry to the UNDO log,
39     可是你不要被此处的UNDO log迷惑了误觉得是磁盘中的文件,其实Innodb代码中的UNDO log,
40     我以为应该理解为UNDO entry,指的是内存中的内容 */
41     trx_undof_page_add_undo_rec_log(undo_page, first_free,
42                     end_of_rec, mtr);
43 
44     return(first_free);
45 }

 trx_undof_page_add_undo_rec_log函数的代码以下:

 1 /************************************************************************
 2 将插入的UNDO条目的mtr日志条目写入到redo log buffer。注释原文是:
 3 Writes the mtr log entry of the inserted undo log record on the undo log page.
 4 可是请注意,这里并非将undo落盘 */
 5 UNIV_INLINE
 6 void
 7 trx_undof_page_add_undo_rec_log(
 8 /*============================*/
 9     page_t* undo_page,    /*!< in: undo log page */
10     ulint    old_free,    /*!< in: start offset of the inserted entry */
11     ulint    new_free,    /*!< in: end offset of the entry */
12     mtr_t*    mtr)        /*!< in: mtr */
13 {
14     byte*        log_ptr;
15     const byte*    log_end;
16     ulint        len;
17 
18     log_ptr = mlog_open(mtr, 11 + 13 + MLOG_BUF_MARGIN);
19 
20     if (log_ptr == NULL) {
21 
22         return;
23     }
24 
25     log_end = &log_ptr[11 + 13 + MLOG_BUF_MARGIN];
26     /*mlog_write_initial_log_record_fast,是mini-transaction相关的函数,用来将redo条目写入到redo log buffer
27     MLOG_UNDO_INSERT,是redo日志类型的一种,是在将一条记录设置为页面中的最小记录时产生的,由于只是打个标记,存储的内容比较简单*/
28     log_ptr = mlog_write_initial_log_record_fast(
29         undo_page, MLOG_UNDO_INSERT, log_ptr, mtr);
30     len = new_free - old_free - 4;
31 
32     mach_write_to_2(log_ptr, len);
33     log_ptr += 2;
34 
35     if (log_ptr + len <= log_end) {
36         memcpy(log_ptr, undo_page + old_free + 2, len);
37         mlog_close(mtr, log_ptr + len);
38     } else {
39         mlog_close(mtr, log_ptr);
40         mlog_catenate_string(mtr, undo_page + old_free + 2, len);
41     }
42 }

 

 

总结

MySQL一条insert操做,会写redo log file和binlog文件,可是不会将UNDO落盘。

UNDO包含在Innodb Buffer Pool中,由Page Cleaner Thread定时刷到磁盘,由Purge Thread定时回收。

相关文章
相关标签/搜索