MySQL · 源码分析 · 一条insert语句的执行过程

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

本文只分析了insert语句执行的主路径,和路径上部分关键函数,很多细节没有深入,留给读者继续分析

create table t1(id int);

insert into t1 values(1)

略过建立连接,从 mysql_parse() 开始分析

void mysql_parse(THD *thd, char *rawbuf, uint length,
 Parser_state *parser_state) {
 /* ...... */ /* 检查query_cache,如果结果存在于cache中,直接返回 */ if (query_cache_send_result_to_client(thd, rawbuf, length) <= 0) 
 {
 LEX *lex= thd->lex;
 	 
 	 /* 解析语句 */ bool err= parse_sql(thd, parser_state, NULL);
		
	 /* 整理语句格式,记录 general log */ /* ...... */ /* 执行语句 */
 error= mysql_execute_command(thd);
 /* 提交或回滚没结束的事务(事务可能在mysql_execute_command中提交,用trx_end_by_hint标记事务是否已经提交) */ if (!thd->trx_end_by_hint) 
 {
 if (!error && lex->ci_on_success)
 trans_commit(thd);
 
 if (error && lex->rb_on_fail)
 trans_rollback(thd);
 }

进入 mysql_execute_command()

 /* */ /* ...... */ case SQLCOM_INSERT:
 { 
 
 /* 检查权限 */ if ((res= insert_precheck(thd, all_tables)))
 break;

 /* 执行insert */
 res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
 lex->update_list, lex->value_list,
 lex->duplicates, lex->ignore);

	/* 提交或者回滚事务 */ if (!res)
 {
 trans_commit_stmt(thd);
 trans_commit(thd);
 thd->trx_end_by_hint= TRUE;
 }
 else if (res)
 {
 trans_rollback_stmt(thd);
 trans_rollback(thd);
 thd->trx_end_by_hint= TRUE;
 }

进入 mysql_insert()

bool mysql_insert(THD *thd,TABLE_LIST *table_list,
 List<Item> &fields, /* insert 的字段 */ List<List_item> &values_list, /* insert 的值 */ List<Item> &update_fields,
 List<Item> &update_values,
 enum_duplicates duplic,
 bool ignore)
{ 
 /*对每条记录调用 write_record */ while ((values= its++))
 {
	if (lock_type == TL_WRITE_DELAYED)
 {
 LEX_STRING const st_query = { query, thd->query_length() };
 DEBUG_SYNC(thd, "before_write_delayed");
 /* insert delay */
 error= write_delayed(thd, table, st_query, log_on, &info);
 DEBUG_SYNC(thd, "after_write_delayed");
 query=0;
 }
 else /* normal insert */
 error= write_record(thd, table, &info, &update);
 }
 
 /*
 这里还有
 thd->binlog_query()写binlog
 my_ok()返回ok报文,ok报文中包含影响行数
 */ 

进入 write_record

/*
 COPY_INFO *info 用来处理唯一键冲突,记录影响行数
 COPY_INFO *update 处理 INSERT ON DUPLICATE KEY UPDATE 相关信息
*/
int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
{
 if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)
 {
 /* 处理 INSERT ON DUPLICATE KEY UPDATE 等复杂情况 */
 }
 /* 调用存储引擎的接口 */ else if ((error=table->file->ha_write_row(table->record[0])))
 {
 DEBUG_SYNC(thd, "write_row_noreplace");
 if (!ignore_errors ||
 table->file->is_fatal_error(error, HA_CHECK_DUP))
 goto err; 
 table->file->restore_auto_increment(prev_insert_id);
 goto ok_or_after_trg_err;
 }
}

进入ha_write_row、write_row

/* handler 是各个存储引擎的基类,这里我们使用InnoDB引擎*/ int handler::ha_write_row(uchar *buf)
{
 /* 指定log_event类型*/
 Log_func *log_func= Write_rows_log_event::binlog_row_logging_function;
 error= write_row(buf);
}

进入引擎层,这里是innodb引擎,handler对应ha_innobase 插入的表信息保存在handler中

int ha_innobase::write_row(
/*===================*/
 uchar* record) /*!< in: a row in MySQL format */
{
		error = row_insert_for_mysql((byte*) record, prebuilt);
}
UNIV_INTERN
dberr_t
row_insert_for_mysql( 
/*=================*/
 byte* mysql_rec, /*!< in: row in the MySQL format */
 row_prebuilt_t* prebuilt) /*!< in: prebuilt struct in MySQL
 handle */
{
		/*记录格式从MySQL转换成InnoDB*/
		row_mysql_convert_row_to_innobase(node->row, prebuilt, mysql_rec);
	
 thr->run_node = node;
 thr->prev_node = node;
		
		/*插入记录*/
 row_ins_step(thr);
}
UNIV_INTERN
que_thr_t*
row_ins_step(
/*=========*/
 que_thr_t* thr) /*!< in: query thread */
{
		/*给表加IX锁*/
		err = lock_table(0, node->table, LOCK_IX, thr);
		
		/*插入记录*/
		err = row_ins(node, thr);
}

InnoDB表是基于B+树的索引组织表

如果InnoDB表没有主键和唯一键,需要分配隐含的row_id组织聚集索引

row_id分配逻辑在row_ins中,这里不详细展开

static __attribute__((nonnull, warn_unused_result))
dberr_t
row_ins(
/*====*/
 ins_node_t* node, /*!< in: row insert node */
 que_thr_t* thr) /*!< in: query thread */
{
		if (node->state == INS_NODE_ALLOC_ROW_ID) {
				/*若innodb表没有主键和唯一键,用row_id组织索引*/
 		row_ins_alloc_row_id_step(node);
				
				/*获取row_id的索引*/
 node->index = dict_table_get_first_index(node->table);
 node->entry = UT_LIST_GET_FIRST(node->entry_list);
		}
		
		/*遍历所有索引,向每个索引中插入记录*/ while (node->index != NULL) {
 if (node->index->type != DICT_FTS) {
 /* 向索引中插入记录 */
 err = row_ins_index_entry_step(node, thr);

 if (err != DB_SUCCESS) {

 return(err);
 }
 } 
				
				/*获取下一个索引*/
 node->index = dict_table_get_next_index(node->index);
 node->entry = UT_LIST_GET_NEXT(tuple_list, node->entry);

 }
 }
}

插入单个索引项

static __attribute__((nonnull, warn_unused_result))
dberr_t
row_ins_index_entry_step( 
/*=====================*/
 ins_node_t* node, /*!< in: row insert node */
 que_thr_t* thr) /*!< in: query thread */
{
 dberr_t err;

 /*给索引项赋值*/
 row_ins_index_entry_set_vals(node->index, node->entry, node->row);

		/*插入索引项*/
 err = row_ins_index_entry(node->index, node->entry, thr);

 return(err);
}
static
dberr_t
row_ins_index_entry( 
/*================*/
 dict_index_t* index, /*!< in: index */
 dtuple_t* entry, /*!< in/out: index entry to insert */
 que_thr_t* thr) /*!< in: query thread */ {

 if (dict_index_is_clust(index)) {
 		/* 插入聚集索引 */ return(row_ins_clust_index_entry(index, entry, thr, 0));
 } else {
 		/* 插入二级索引 */ return(row_ins_sec_index_entry(index, entry, thr));
 }
}

row_ins_clust_index_entry 和 row_ins_sec_index_entry 函数结构类似,只分析插入聚集索引

UNIV_INTERN
dberr_t
row_ins_clust_index_entry(
/*======================*/
 dict_index_t* index, /*!< in: clustered index */
 dtuple_t* entry, /*!< in/out: index entry to insert */
 que_thr_t* thr, /*!< in: query thread */
 ulint n_ext) /*!< in: number of externally stored columns */
{
 if (UT_LIST_GET_FIRST(index->table->foreign_list)) {
 err = row_ins_check_foreign_constraints(
 index->table, index, entry, thr);
 if (err != DB_SUCCESS) {
 return(err);
 }
 }
 
 /* flush log,make checkpoint(如果需要) */
 log_free_check();

		/* 先尝试乐观插入,修改叶子节点 BTR_MODIFY_LEAF */
 err = row_ins_clust_index_entry_low(
 0, BTR_MODIFY_LEAF, index, n_uniq, entry, n_ext, thr, 
 &page_no, &modify_clock);
 
 if (err != DB_FAIL) {
 DEBUG_SYNC_C("row_ins_clust_index_entry_leaf_after");
 return(err);
 } 
		
		/* flush log,make checkpoint(如果需要) */
 log_free_check();

		/* 乐观插入失败,尝试悲观插入 BTR_MODIFY_TREE */ return(row_ins_clust_index_entry_low(
 0, BTR_MODIFY_TREE, index, n_uniq, entry, n_ext, thr,
 &page_no, &modify_clock));

row_ins_clust_index_entry_low 和 row_ins_sec_index_entry_low 函数结构类似,只分析插入聚集索引

UNIV_INTERN
dberr_t
row_ins_clust_index_entry_low(
/*==========================*/
 ulint flags, /*!< in: undo logging and locking flags */
 ulint mode, /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE,
 depending on whether we wish optimistic or
 pessimistic descent down the index tree */
 dict_index_t* index, /*!< in: clustered index */
 ulint n_uniq, /*!< in: 0 or index->n_uniq */
 dtuple_t* entry, /*!< in/out: index entry to insert */
 ulint n_ext, /*!< in: number of externally stored columns */
 que_thr_t* thr, /*!< in: query thread */
 ulint* page_no,/*!< *page_no and *modify_clock are used to decide
 whether to call btr_cur_optimistic_insert() during
 pessimistic descent down the index tree.
 in: If this is optimistic descent, then *page_no
 must be ULINT_UNDEFINED. If it is pessimistic
 descent, *page_no must be the page_no to which an
 optimistic insert was attempted last time
 row_ins_index_entry_low() was called.
 out: If this is the optimistic descent, *page_no is set
 to the page_no to which an optimistic insert was
 attempted. If it is pessimistic descent, this value is
 not changed. */
 ullint* modify_clock) /*!< in/out: *modify_clock == ULLINT_UNDEFINED
 during optimistic descent, and the modify_clock
 value for the page that was used for optimistic
 insert during pessimistic descent */
{
		/* 将cursor移动到索引上待插入的位置 */
		btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE, mode, 
 &cursor, 0, __FILE__, __LINE__, &mtr);
 
 /*根据不同的flag检查主键冲突*/
 err = row_ins_duplicate_error_in_clust_online(
 n_uniq, entry, &cursor,
 &offsets, &offsets_heap);
 
 err = row_ins_duplicate_error_in_clust(
 flags, &cursor, entry, thr, &mtr);

		/*
		 如果要插入的索引项已存在,则把insert操作改为update操作
		 索引项已存在,且没有主键冲突,是因为之前的索引项对应的数据被标记为已删除
		 本次插入的数据和上次删除的一样,而索引项并未删除,所以变为update操作		
		*/ if (row_ins_must_modify_rec(&cursor)) {
 /* There is already an index entry with a long enough common
 prefix, we must convert the insert into a modify of an
 existing record */
 mem_heap_t* entry_heap = mem_heap_create(1024);
				
				/* 更新数据到存在的索引项 */
 err = row_ins_clust_index_entry_by_modify(
 flags, mode, &cursor, &offsets, &offsets_heap,
 entry_heap, &big_rec, entry, thr, &mtr);
 
 /*如果索引正在online_ddl,先记录insert*/ if (err == DB_SUCCESS && dict_index_is_online_ddl(index)) {
 row_log_table_insert(rec, index, offsets);
 }

				/*提交mini transaction*/
 mtr_commit(&mtr);
 mem_heap_free(entry_heap);
 } else {
 rec_t* insert_rec;

 if (mode != BTR_MODIFY_TREE) {
 		/*进行一次乐观插入*/
 err = btr_cur_optimistic_insert(
 flags, &cursor, &offsets, &offsets_heap,
 entry, &insert_rec, &big_rec,
 n_ext, thr, &mtr);
 } else {
 		/*
 		 如果buffer pool余量不足25%,插入失败,返回DB_LOCK_TABLE_FULL
 		 处理DB_LOCK_TABLE_FULL错误时,会回滚事务
 		 防止大事务的锁占满buffer pool(注释里写的)
 		*/ if (buf_LRU_buf_pool_running_out()) {

 err = DB_LOCK_TABLE_FULL;
 goto err_exit;
 }

 if (/*太长了,略*/) {
 		 /*进行一次乐观插入*/
 err = btr_cur_optimistic_insert(
 flags, &cursor,
 &offsets, &offsets_heap,
 entry, &insert_rec, &big_rec,
 n_ext, thr, &mtr);
 } else {
 err = DB_FAIL;
 }

 if (err == DB_FAIL) {
 		 /*乐观插入失败,进行悲观插入*/
 err = btr_cur_pessimistic_insert(
 flags, &cursor,
 &offsets, &offsets_heap,
 entry, &insert_rec, &big_rec,
 n_ext, thr, &mtr);
 }
 }

}

btr_cur_optimistic_insert 和 btr_cur_pessimistic_insert 涉及B+树的操作,内部细节很多,以后再做分析

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
SQL 关系型数据库 MySQL
MySQL技能完整学习列表3、SQL语言基础——1、SQL(Structured Query Language)简介——2、基本SQL语句:SELECT、INSERT、UPDATE、DELETE
MySQL技能完整学习列表3、SQL语言基础——1、SQL(Structured Query Language)简介——2、基本SQL语句:SELECT、INSERT、UPDATE、DELETE
49 0
|
4月前
|
SQL 缓存 关系型数据库
执行一个SQL,MySQL内部的执行过程是什么?
执行一个SQL,MySQL内部的执行过程是什么?
33 0
|
3月前
|
SQL 存储 缓存
SQL语句在MySQL中是如何执行的
SQL语句在MySQL中是如何执行的
45 0
|
1月前
|
SQL 存储 缓存
【SQL】Mysql中一条sql语句的执行过程
【SQL】Mysql中一条sql语句的执行过程
30 0
|
8月前
|
存储 SQL 缓存
MySQL 中一条 SQL 查询语句的执行过程
`SELECT id FROM table_a where id = 10` 这条 SQL 从执行到最后结果返回你知道都经历了哪些步骤么?
MySQL 中一条 SQL 查询语句的执行过程
|
4月前
|
SQL 关系型数据库 MySQL
|
4月前
|
关系型数据库 MySQL
零基础带你学习MySQL—Insert语句以及注意事项(七)
零基础带你学习MySQL—Insert语句以及注意事项(七)
|
8月前
|
关系型数据库 MySQL 数据库
数据的查询与添加:解析MySQL中的SELECT和INSERT操作
在数据库管理中,SELECT和INSERT操作是日常工作中的重要环节,用于数据的获取和添加。了解如何正确使用这两个操作是数据库操作的关键。
132 0
|
8月前
|
关系型数据库 MySQL 数据库
数据的添加与插入:探究MySQL中的INSERT操作
在数据库管理中,添加新数据是一个常见且关键的操作,而"INSERT"语句正是用于实现这一目标的命令。MySQL中的INSERT操作可以让我们在数据库表中添加新的数据记录。
90 0
|
8月前
|
存储 关系型数据库 MySQL
MySQL中的INSERT INTO SELECT语法及其用法详解
当今的数据库管理系统在数据存储和检索方面起着关键作用,而MySQL作为最受欢迎的开源关系型数据库管理系统之一,提供了许多强大的功能。在MySQL中,INSERT INTO SELECT语法是一种非常有用的功能,可以将查询结果直接插入到目标表中。本文将介绍MySQL中的INSERT INTO SELECT语法及其用法。
277 0