DML操作的大致流程

在解答上述疑惑之前,我们来梳理一下DML操作的大致流程:

1、语法解析、语义解析

2、生成执行计划

3、事务修改阶段

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

  2) 查找定位数据

  3) 乐观插入

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

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

  6) binlog event 写入到 binlog cache

4、事务提交阶段

  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等工具来验证。

以下是源码浅析:

插入的流程:

     //trx_undof_page_add_undo_rec_log--记录undo的redo log 入redo buffer
> mysqld.exe!trx_undof_page_add_undo_rec_log(unsigned char * undo_page, unsigned __int64 old_free, unsigned __int64 new_free, mtr_t * mtr) 行
mysqld.exe!trx_undo_page_set_next_prev_and_add(unsigned char * undo_page, unsigned char * ptr, mtr_t * mtr) 行
//trx_undo_page_report_insert--记录insert的undo记录
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) 行
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) 行
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) 行
//btr_cur_optimistic_insert--进行乐观插入
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) 行
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) 行
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) 行
mysqld.exe!row_ins_index_entry(dict_index_t * index, dtuple_t * entry, que_thr_t * thr) 行
mysqld.exe!row_ins_index_entry_step(ins_node_t * node, que_thr_t * thr) 行
mysqld.exe!row_ins(ins_node_t * node, que_thr_t * thr) 行
mysqld.exe!row_ins_step(que_thr_t * thr) 行
mysqld.exe!row_insert_for_mysql_using_ins_graph(const unsigned char * mysql_rec, row_prebuilt_t * prebuilt) 行
mysqld.exe!row_insert_for_mysql(const unsigned char * mysql_rec, row_prebuilt_t * prebuilt) 行
mysqld.exe!ha_innobase::write_row(unsigned char * record) 行
mysqld.exe!handler::ha_write_row(unsigned char * buf) 行
mysqld.exe!write_record(THD * thd, TABLE * table, COPY_INFO * info, COPY_INFO * update) 行
mysqld.exe!Sql_cmd_insert::mysql_insert(THD * thd, TABLE_LIST * table_list) 行
mysqld.exe!Sql_cmd_insert::execute(THD * thd) 行
mysqld.exe!mysql_execute_command(THD * thd, bool first_level) 行
mysqld.exe!mysql_parse(THD * thd, Parser_state * parser_state) 行
mysqld.exe!dispatch_command(THD * thd, const COM_DATA * com_data, enum_server_command command) 行
mysqld.exe!do_command(THD * thd) 行
mysqld.exe!handle_connection(void * arg) 行
mysqld.exe!pfs_spawn_thread(void * arg) 行
mysqld.exe!win_thread_start(void * p) 行

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

 /**********************************************************************//**
在UNDO日志中报告聚集索引记录的插入。注意:这里的UNDO日志,指的是内存中的数据结构
@return在页面上插入的条目的偏移量(如果成功),如果失败则为0 */
static
ulint
trx_undo_page_report_insert(
/*========================*/
page_t* undo_page, /*!< in: undo log page */
trx_t* trx, /*!< in: transaction */
dict_index_t* index, /*!< in: clustered index */
const dtuple_t* clust_entry, /*!< in: index entry which will be
inserted to the clustered index */
mtr_t* mtr) /*!< in: mtr */
{
ulint first_free;
byte* ptr;
ulint i; //...省略若干内容 /* 预留2字节给指向下一条UNDO日志的指针 */
ptr += ; /* Store first some general parameters to the undo log */
*ptr++ = TRX_UNDO_INSERT_REC;
ptr += mach_u64_write_much_compressed(ptr, trx->undo_no);
ptr += mach_u64_write_much_compressed(ptr, index->table->id);
/*----------------------------------------*/
/* 然后存储唯一确定要在聚簇索引中插入的记录所需的字段 */ for (i = ; i < dict_index_get_n_unique(index); i++) { const dfield_t* field = dtuple_get_nth_field(clust_entry, i);
ulint flen = dfield_get_len(field); if (trx_undo_left(undo_page, ptr) < ) { return();
} ptr += mach_write_compressed(ptr, flen); if (flen != UNIV_SQL_NULL) {
if (trx_undo_left(undo_page, ptr) < flen) { return();
} ut_memcpy(ptr, dfield_get_data(field), flen);
ptr += flen;
}
} if (index->table->n_v_cols) {
if (!trx_undo_report_insert_virtual(
undo_page, index->table, clust_entry, &ptr)) {
return();
}
}
/* 调用trx_undo_page_set_next_prev_and_add函数 */
return(trx_undo_page_set_next_prev_and_add(undo_page, ptr, mtr));
}

trx_undo_page_set_next_prev_and_add函数的代码如下:

 /**********************************************************************//**
在UNDO page中为写入到ptr的撤消记录设置下一个和上一个指针。 通过为此UNDO日志写入的字节数更新第一个空闲值。
@return在页面上插入的条目的偏移量(如果成功),如果失败则为0 */
static
ulint
trx_undo_page_set_next_prev_and_add(
/*================================*/
page_t* undo_page, /*!< in/out: undo log page */
byte* ptr, /*!< in: ptr up to where data has been
written on this undo page. */
mtr_t* mtr) /*!< in: mtr */
{
ulint first_free; /*!< offset within undo_page */
ulint end_of_rec; /*!< offset within undo_page */
byte* ptr_to_first_free;
/* pointer within undo_page
that points to the next free
offset value within undo_page.*/ //...省略若干代码 ptr_to_first_free = undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE; first_free = mach_read_from_2(ptr_to_first_free); /* 写入上一个UNDO日志记录的偏移量 */
mach_write_to_2(ptr, first_free);
ptr += ; end_of_rec = ptr - undo_page; /* 写入下一个UNDO日志记录的偏移量 */
mach_write_to_2(undo_page + first_free, end_of_rec); /* 将偏移量更新为第一个空闲的UNDO记录 */
mach_write_to_2(ptr_to_first_free, end_of_rec); /* 将此日志条目写入UNDO日志,注释原文是Write this log entry to the UNDO log,
但是你不要被此处的UNDO log迷惑了误以为是磁盘中的文件,其实Innodb代码中的UNDO log,
我觉得应该理解为UNDO entry,指的是内存中的内容 */
trx_undof_page_add_undo_rec_log(undo_page, first_free,
end_of_rec, mtr); return(first_free);
}

trx_undof_page_add_undo_rec_log函数的代码如下:

 /************************************************************************
将插入的UNDO条目的mtr日志条目写入到redo log buffer。注释原文是:
Writes the mtr log entry of the inserted undo log record on the undo log page.
但是请注意,这里并不是将undo落盘 */
UNIV_INLINE
void
trx_undof_page_add_undo_rec_log(
/*============================*/
page_t* undo_page, /*!< in: undo log page */
ulint old_free, /*!< in: start offset of the inserted entry */
ulint new_free, /*!< in: end offset of the entry */
mtr_t* mtr) /*!< in: mtr */
{
byte* log_ptr;
const byte* log_end;
ulint len; log_ptr = mlog_open(mtr, + + MLOG_BUF_MARGIN); if (log_ptr == NULL) { return;
} log_end = &log_ptr[ + + MLOG_BUF_MARGIN];
/*mlog_write_initial_log_record_fast,是mini-transaction相关的函数,用来将redo条目写入到redo log buffer
MLOG_UNDO_INSERT,是redo日志类型的一种,是在将一条记录设置为页面中的最小记录时产生的,因为只是打个标记,存储的内容比较简单*/
log_ptr = mlog_write_initial_log_record_fast(
undo_page, MLOG_UNDO_INSERT, log_ptr, mtr);
len = new_free - old_free - ; mach_write_to_2(log_ptr, len);
log_ptr += ; if (log_ptr + len <= log_end) {
memcpy(log_ptr, undo_page + old_free + , len);
mlog_close(mtr, log_ptr + len);
} else {
mlog_close(mtr, log_ptr);
mlog_catenate_string(mtr, undo_page + old_free + , len);
}
}

总结

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

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

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

  1. Spring源码分析——JdbcTemplate执行批量insert操作

    最近用到一个方法: @Override public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) thr ...

  2. MySQL多版本并发控制机制(MVCC)-源码浅析

    MySQL多版本并发控制机制(MVCC)-源码浅析 前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾.<<事务处理-概念与技术>>诚然 ...

  3. centos 6x系统下源码安装mysql操作记录

    在运维工作中经常部署各种运维环境,涉及mysql数据库的安装也是时常需要的.mysql数据库安装可以选择yum在线安装,但是这种安装的mysql一般是系统自带的,版本方面可能跟需求不太匹配.可以通过源 ...

  4. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  5. Centos7源码安装mysql及读写分离,互为主从

       Linux服务器 -源码安装mysql 及读写分离,互为主从   一.环境介绍: Linux版本: CentOS 7 64位 mysq版本: mysql-5.6.26 这是我安装时所使用的版本, ...

  6. Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境

    Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Andr ...

  7. 源码安装mysql,及主从同步

    源码安装mysql [可选] 如果用源码安装cmake软件: cd /home/oldboy/tools/ tar xf cmake-.tar.gz cd cmake- ./configure #CM ...

  8. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  9. CentOS 7下源码安装MySQL 5.7

    网上说linux安装mysql服务分两种安装方法: ①源码安装,优点是安装包比较小,只有几十M左右,缺点是安装依赖的库多,安装编译时间长,安装步骤复杂容易出错: ②使用官方编译好的二进制文件安装,优点 ...

随机推荐

  1. 插入数据值 设置标签属性的值 来自 精通ASP-NET-MVC-5-弗瑞曼

  2. basic-pentesting-1 靶机提权

    原文地址:https://www.payload.com.cn/   basic-pentesting-1 下载地址: https://www.vulnhub.com/entry/basic-pent ...

  3. 架构模式中的Active Record和Data Mapper

    架构模式中的Active Record和Data Mapper 概念 在简单应用中,领域模型是一种和数据库结构一致的简单结构,对应每个数据库表都有一个领域类,在这种情况下,有必要让每个对象负责数据库的 ...

  4. java基础算法(一):最大子序列和问题的多种算法思路

    问题: /** * 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和,并顺序打印子序列. * 示例: * 输入: [-2,1,-3,4,-1,2,1 ...

  5. oracle问题之死锁 (一)

    [前言] 遇到 oracle 异常 和 解决实践 系列文章 整理分享 杂症一.oracle死锁 一.症状: 执行SQL或程序时,程序没有响应或SQL执行一直处于执行状态,没有成功,也没有报错. 二.病 ...

  6. MFC对话框和控件

    对话框和控件 对话框是Windows应用程序中一种常用的资源,其主要功能是输出信息和接收用户的输入数据.控件是嵌入在对话框中或其它父窗口中的一个特殊的小窗口,它用于完成不同的输入.输出功能.对话框与控 ...

  7. RestTemplate工具类根据服务名发送请求

    要使用RestTemplate 根据服务名发送请求的话需要 使用  @LoadBalanced  这个注解,用了这个注解的RestTemplate就不用使用  ip 来请求了,首先要创建一个配置类 i ...

  8. CCF_ 201512-4_送货

    一道拖了很久了题,用bool开的vis不会爆内存,dfs回溯的话会超时,于是有了一个很巧妙的dfs. #include<iostream> #include<cstring> ...

  9. 51Nod 1021 石子归并(区间dp经典入门)

    题意: N堆石子摆成一条线.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价.计算将N堆石子合并成一堆的最小代价. n<=100 思 ...

  10. 如何用一月6RMB搭建一个国外服务器

    转载自我的博客:https://blog.ljyngup.com 前言 本文将教你如何用一月6RMB的价格搭建一个属于个人的外国服务器.并且一月500G流量,延迟低于500ms. 开始 导航:Virm ...