摘要

这个官方文档一段对MySQL内核分析的一个向导。是对MySQL一条insert语句写入到MySQL数据库的分析。

但是,对于MySQL 5.7版本来说,基本上都是写入到innodb引擎。但也还是有借鉴意义,大的框架没有太大变化。

后面的文档,会通过mysqld --debug 和gdb等工具,通过分析mysqld.trace来分析insert语句在MySQL 5.7中怎么写入数据库。

官方文档给出的一段结构,如下:

  1. /sql/mysqld.cc
  2. /sql/sql_parse.cc
  3. /sql/sql_prepare.cc
  4. /sql/sql_insert.cc
  5. /sql/ha_myisam.cc
  6. /myisam/mi_write.c

上述梳理一个过程,是说从客户段执行一条简单的insert语句,然后到达MySQL服务器端,并通过MyISAM存储层。写入到MyISAM文件的过程。

由于,我们现在的主流都是InnoDB存储引擎,所以我们分析的写入到存储层应该是InnoDB的源代码。但是上述的一个框架也有借鉴意义。虽然,走的是InnoDB存储引擎插入数据,但是也还是需要通过SQL层的ha_*这样的接口进行接入。

正题开始!!!!!!!!!!!!!!!!!!!!!!!

第一步,进入MySQL大门的地方。梦开始的地方。众所周知,C语言都是需要main方法作为主入口。而MySQL的主入口如下:

代码位置 /sql/mysqld.cc

  1. int main(int argc, char **argv)
  2. {
  3. _cust_check_startup();
  4. (void) thr_setconcurrency(concurrency);
  5. init_ssl();
  6. server_init(); // 'bind' + 'listen'
  7. init_server_components();
  8. start_signal_handler();
  9. acl_init((THD *)0, opt_noacl);
  10. init_slave();
  11. create_shutdown_thread();
  12. create_maintenance_thread();
  13. handle_connections_sockets(0); // ! 这里也代表着我们进入下一个门的地方
  14. DBUG_PRINT("quit",("Exiting main thread"));
  15. exit(0);
  16. }

这里可以看到很多的init_*或者server_init()。通过名字我们可以猜测出,这里做了很多初始化的工作。例如:启动过程中一些初始化的检查和MySQL配置变量的加载和一些组件的初始化等。

这里重要的函数是handle_connections_sockets

继续跟踪 /sql/mysqld.cc

  1. handle_connections_sockets (arg __attribute__((unused))
  2. {
  3. if (ip_sock != INVALID_SOCKET)
  4. {
  5. FD_SET(ip_sock,&clientFDs);
  6. DBUG_PRINT("general",("Waiting for connections."));
  7. while (!abort_loop)
  8. {
  9. new_sock = accept(sock, my_reinterpret_cast(struct sockaddr*)
  10. (&cAddr), &length);
  11. thd= new THD;
  12. if (sock == unix_sock)
  13. thd->host=(char*) localhost;
  14. create_new_thread(thd); // !
  15. }

从简易的思维,忽视其他的判断语句。可以看到这里做的是典型的client/server架构。服务器有一个主线程,它总是侦听来自新客户机的请求。一旦它接收到这样的请求,它将分配资源。特别是,主线程将生成一个新线程来处理连接。然后主服务器将循环并侦听新连接——但我们将保留它并跟踪新线程。

这里创建新线程的方法是:create_new_thread(thd);

继续跟踪 /sql/mysqld.cc

  1. create_new_thread(THD *thd)
  2. {
  3. pthread_mutex_lock(&LOCK_thread_count);
  4. pthread_create(&thd->real_id,&connection_attrib,
  5. handle_one_connection, // !
  6. (void*) thd));
  7. pthread_mutex_unlock(&LOCK_thread_count);
  8. }

可以看到这里获得一个新线程加入一个互斥锁,避免冲突。

继续跟踪 /sql/mysqld.cc

  1. handle_one_connection(THD *thd)
  2. {
  3. init_sql_alloc(&thd->mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
  4. while (!net->error && net->vio != 0 && !thd->killed)
  5. {
  6. if (do_command(thd)) // !
  7. break;
  8. }
  9. close_connection(net);
  10. end_thread(thd,1);
  11. packet=(char*) net->read_pos;

从这里开始,我们即将脱离mysqld.cc文件,因为我们获得了thread,且分配一小段内存资源,给与我们来处理我们的SQL语句了。

我们会走向何方呢,可以开始观察do_command(thd)方法。

继续跟踪/sql/sql_parse.cc

  1. bool do_command(THD *thd)
  2. {
  3. net_new_transaction(net);
  4. packet_length=my_net_read(net);
  5. packet=(char*) net->read_pos;
  6. command = (enum enum_server_command) (uchar) packet[0];
  7. dispatch_command(command,thd, packet+1, (uint) packet_length);
  8. // !
  9. }

其中从这里可以看到,do_command(THD *thd)把它串联起来的是一个叫作THD的东西,也就是thread。所以后面的工作和行为,基本都是通过thread进行牵线搭桥的。

my_net_read函数位于另一个名为net_servlet .cc的文件中。该函数从客户端获取一个包,解压缩它,并去除头部。

一旦完成,我们就得到了一个名为packet的多字节变量,它包含客户端发送的内容。第一个字节很重要,因为它包含标识消息类型的代码。

说明了packet第一个字节很重要。debug也有证据进行一个佐证。

  1. packet_header: Memory: 0x7f7fc000a4b0 Bytes: (4)
  2. 21 00 00 00

然后把packet第一个字节和余下的部分传递给dispatch_command

继续跟踪/sql/sql_parse.cc

  1. bool dispatch_command(enum enum_server_command command, THD *thd,
  2. char* packet, uint packet_length)
  3. {
  4. switch (command) {
  5. case COM_INIT_DB: ...
  6. case COM_REGISTER_SLAVE: ...
  7. case COM_TABLE_DUMP: ...
  8. case COM_CHANGE_USER: ...
  9. case COM_EXECUTE:
  10. mysql_stmt_execute(thd,packet);
  11. case COM_LONG_DATA: ...
  12. case COM_PREPARE:
  13. mysql_stmt_prepare(thd, packet, packet_length); // !
  14. /* and so on for 18 other cases */
  15. default:
  16. send_error(thd, ER_UNKNOWN_COM_ERROR);
  17. break;
  18. }

这里sql_parser .cc中有一个非常大的switch语句

switch语句中代码有:code for prepare, close statement, query, quit, create database, drop database, dump binary log, refresh, statistics, get process info, kill process, sleep, connect, and several minor commands

除了COM_EXECUTE和COM_PREPARE两种情况外,我们删除了所有情况下的代码细节。

可以看到

  • COM_EXECUTE 会调用mysql_stmt_execute(thd,packet);

  • COM_PREPARE 会调用mysql_stmt_prepare(thd, packet, packet_length);

这里就像一个中转站一般,看我们去向什么地方。这里去的门是:COM_PREPARE:mysql_stmt_prepare

跟踪 /sql/sql_prepare.cc

下面是一段prepare的注释

  1. "Prepare:
  2. Parse the query
  3. Allocate a new statement, keep it in 'thd->prepared statements' pool
  4. Return to client the total number of parameters and result-set
  5. metadata information (if any)"

继续回到主线COM_EXECUTE

跟踪/sql/sql_parse.cc

  1. bool dispatch_command(enum enum_server_command command, THD *thd,
  2. char* packet, uint packet_length)
  3. {
  4. switch (command) {
  5. case COM_INIT_DB: ...
  6. case COM_REGISTER_SLAVE: ...
  7. case COM_TABLE_DUMP: ...
  8. case COM_CHANGE_USER: ...
  9. case COM_EXECUTE:
  10. mysql_stmt_execute(thd,packet); // !
  11. case COM_LONG_DATA: ...
  12. case COM_PREPARE:
  13. mysql_stmt_prepare(thd, packet, packet_length);
  14. /* and so on for 18 other cases */
  15. default:
  16. send_error(thd, ER_UNKNOWN_COM_ERROR);
  17. break;
  18. }

现在``COM_EXECUTE 中的mysql_stmt_execute`是我们关注的重点,我们来看看

跟踪/sql/sql_prepare.cc代码

  1. void mysql_stmt_execute(THD *thd, char *packet)
  2. {
  3. if (!(stmt=find_prepared_statement(thd, stmt_id, "execute")))
  4. {
  5. send_error(thd);
  6. DBUG_VOID_RETURN;
  7. }
  8. init_stmt_execute(stmt);
  9. mysql_execute_command(thd); // !
  10. }

这里做一个判断,看是否是execute,然后初始化语句,并开始执行mysql_execute_command(thd);可以看到,是通过thread来调用动作。

跟踪/sql/sql_parse.cc代码

  1. void mysql_execute_command(THD *thd)
  2. switch (lex->sql_command) {
  3. case SQLCOM_SELECT: ...
  4. case SQLCOM_SHOW_ERRORS: ...
  5. case SQLCOM_CREATE_TABLE: ...
  6. case SQLCOM_UPDATE: ...
  7. case SQLCOM_INSERT: ... // !
  8. case SQLCOM_DELETE: ...
  9. case SQLCOM_DROP_TABLE: ...
  10. }

lex 解析sql语句。然后进入SQLCOM_INSERT。

跟踪/sql/sql_parse.cc代码

  1. case SQLCOM_INSERT:
  2. {
  3. my_bool update=(lex->value_list.elements ? UPDATE_ACL : 0);
  4. ulong privilege= (lex->duplicates == DUP_REPLACE ?
  5. INSERT_ACL | DELETE_ACL : INSERT_ACL | update);
  6. if (check_access(thd,privilege,tables->db,&tables->grant.privilege))
  7. goto error;
  8. if (grant_option && check_grant(thd,privilege,tables))
  9. goto error;
  10. if (select_lex->item_list.elements != lex->value_list.elements)
  11. {
  12. send_error(thd,ER_WRONG_VALUE_COUNT);
  13. DBUG_VOID_RETURN;
  14. }
  15. res = mysql_insert(thd,tables,lex->field_list,lex->many_values,
  16. select_lex->item_list, lex->value_list,
  17. (update ? DUP_UPDATE : lex->duplicates));
  18. // !
  19. if (thd->net.report_error)
  20. res= -1;
  21. break;
  22. }

对于插入数据,我们要做的第一件事情是:检查用户是否具有对表进行插入的适当特权,服务器通过调用check_access和check_grant函数在这里进行检查。

有了权限才可以做【插入】动作。

我们可以导航 /sql 目录,如下:

  1. Program Name SQL statement type
  2. ------------ ------------------
  3. sql_delete.cc DELETE
  4. sql_do.cc DO
  5. sql_handler.cc HANDLER
  6. sql_help.cc HELP
  7. sql_insert.cc INSERT // !
  8. sql_load.cc LOAD
  9. sql_rename.cc RENAME
  10. sql_select.cc SELECT
  11. sql_show.cc SHOW
  12. sql_update.cc UPDATE

sql_insert.cc是具体执行插入的操作。

上面的mysql_insert() 的方法具体实现,在sql_insert.cc文件中。

跟踪 /sql/sql_insert.cc代码

  1. int mysql_insert(THD *thd,TABLE_LIST *table_list, List<Item> &fields,
  2. List<List_item> &values_list,enum_duplicates duplic)
  3. {
  4. table = open_ltable(thd,table_list,lock_type);
  5. if (check_insert_fields(thd,table,fields,*values,1) ||
  6. setup_tables(table_list) ||
  7. setup_fields(thd,table_list,*values,0,0,0))
  8. goto abort;
  9. fill_record(table->field,*values);
  10. error=write_record(table,&info); // !
  11. query_cache_invalidate3(thd, table_list, 1);
  12. if (transactional_table)
  13. error=ha_autocommit_or_rollback(thd,error);
  14. query_cache_invalidate3(thd, table_list, 1);
  15. mysql_unlock_tables(thd, thd->lock);
  16. }

这里就要开始,打开一张表。然后各种检查,看插入表的字段是否有问题。不行就abort。

然后,开始填充记录数据。最终调用write_record 写记录的方法。

由于write_record 会对应不同的存储引擎,所以这里有分支的。我这里讲解两种

继续跟踪/sql/sql_insert.cc

  1. int write_record(TABLE *table,COPY_INFO *info)
  2. {
  3. table->file->write_row(table->record[0]; // !
  4. }

终于,要写文件了。调用那个存储引擎呢?看handler.h

  1. /* The handler for a table type.
  2. Will be included in the TABLE structure */
  3. handler(TABLE *table_arg) :
  4. table(table_arg),active_index(MAX_REF_PARTS),
  5. ref(0),ref_length(sizeof(my_off_t)),
  6. block_size(0),records(0),deleted(0),
  7. data_file_length(0), max_data_file_length(0),
  8. index_file_length(0),
  9. delete_length(0), auto_increment_value(0), raid_type(0),
  10. key_used_on_scan(MAX_KEY),
  11. create_time(0), check_time(0), update_time(0), mean_rec_length(0),
  12. ft_handler(0)
  13. {}
  14. ...
  15. virtual int write_row(byte * buf)=0;

写入之MyISAM的代码路径

官方文档默认调用的是 ha_myisam::write_row

代码 /sql/ha_myisam.cc

如下:

  1. int ha_myisam::write_row(byte * buf)
  2. {
  3. statistic_increment(ha_write_count,&LOCK_status);
  4. /* If we have a timestamp column, update it to the current time */
  5. if (table->time_stamp)
  6. update_timestamp(buf+table->time_stamp-1);
  7. /*
  8. If we have an auto_increment column and we are writing a changed row
  9. or a new row, then update the auto_increment value in the record.
  10. */
  11. if (table->next_number_field && buf == table->record[0])
  12. update_auto_increment();
  13. return mi_write(file,buf); // !
  14. }

这些以字母ha开头的程序是处理程序的接口,而这个程序是myisam处理程序的接口。我们这里就开始调用MyISAM了。

可以看到这里调用了mi_write(file,buf);

跟踪/myisam/mi_write.c

  1. int mi_write(MI_INFO *info, byte *record)
  2. {
  3. _mi_readinfo(info,F_WRLCK,1);
  4. _mi_mark_file_changed(info);
  5. /* Calculate and check all unique constraints */
  6. for (i=0 ; i < share->state.header.uniques ; i++)
  7. {
  8. mi_check_unique(info,share->uniqueinfo+i,record,
  9. mi_unique_hash(share->uniqueinfo+i,record),
  10. HA_OFFSET_ERROR);
  11. }
  12. ... to be continued in next snippet

这里有很多唯一性的校验,继续看下面

  1. ... continued from previous snippet
  2. /* Write all keys to indextree */
  3. for (i=0 ; i < share->base.keys ; i++)
  4. {
  5. share->keyinfo[i].ck_insert(info,i,buff,
  6. _mi_make_key(info,i,buff,record,filepos)
  7. }
  8. (*share->write_record)(info,record);
  9. if (share->base.auto_key)
  10. update_auto_increment(info,record);
  11. }

这里就是我们写入到文件的地方。至此,MySQL的插入操作结束。

路径为:

  1. main in /sql/mysqld.cc
  2. handle_connections_sockets in /sql/mysqld.cc
  3. create_new_thread in /sql/mysqld.cc
  4. handle_one_connection in /sql/sql_parse.cc
  5. do_command in /sql/sql_parse.cc
  6. dispatch_command in /sql/sql_parse.cc
  7. mysql_stmt_execute in /sql/sql_prepare.cc
  8. mysql_execute_command in /sql/sql_parse.cc
  9. mysql_insert in /sql/mysql_insert.cc
  10. write_record in /sql/mysql_insert.cc
  11. ha_myisam::write_row in /sql/ha_myisam.cc
  12. mi_write in /myisam/mi_write.c

1.进入主函数入口

2.建立socket connection的请求

3.创建一个新的线程

4.处理线程,分配内存资源

5.do_command,是获取packet第一字节,看做什么操作,并接受余下字节。

6.dispatch_command,分发操作,这里分发的是insert。

7.mysql_stmt_execute,检查是否为execute,初始化,准备做execute动作。

8.mysql_execute_command ,lex解析SQL语句,进入到SQLCOM_INSERT

9.mysql_insert ,开始做插入操作。调用write_record

10.write_record,准备写入,看调用哪个存储引擎,写入前期准备工作

11.ha_myisam::write_row,ha_myisam进行插入写入。

12.mi_write,最后做写入操作。

文献参考:https://dev.mysql.com/doc/internals/en/guided-tour-skeleton.html

01MySQL内核分析-The Skeleton of the Server Code的更多相关文章

  1. 《Linux内核分析》第三周 构建一个简单的Linux系统MenuOS

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK THREE ...

  2. 【课程总结】Linux内核分析课程总结

    程涵  原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 每周实验报告: 反汇编一个简单的C程序 ...

  3. “Linux内核分析”实验三报告

    构造一个简单的Linux系统 张文俊+原创作品转载请注明出处+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10000290 ...

  4. 《Linux内核分析》第三周学习小结 构造一个简单的Linux系统OS

    郝智宇 无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 第三周 构造一个简单的Linux系统Me ...

  5. Linux内核分析 NO.3

    跟踪分析Linux内核的启动过程 于佳心 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002 ...

  6. Linux内核分析——第三周学习笔记

    20135313吴子怡.北京电子科技学院 chapter1 知识点梳理 一.Linux内核源代码简介 (视频中对目录下的文件进行了简介,记录如下) arch目录 占有相当庞大的空间 arch/x86目 ...

  7. linux内核分析作业8:理解进程调度时机跟踪分析进程调度与进程切换的过程

    1. 实验目的 选择一个系统调用(13号系统调用time除外),系统调用列表,使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 分析汇编代码调用系统调用的工作过程,特别是参数的传递的方 ...

  8. Linux内核分析作业7:Linux内核如何装载和启动一个可执行程序

            1.可执行文件的格式 在 Linux 平台下主要有以下三种可执行文件格式: 1.a.out(assembler and link editor output 汇编器和链接编辑器的输出) ...

  9. linux内核分析作业6:分析Linux内核创建一个新进程的过程

    task_struct结构: struct task_struct {   volatile long state;进程状态  void *stack; 堆栈  pid_t pid; 进程标识符  u ...

随机推荐

  1. 技术大佬:我去,这个容易被忽略的小程序Image图片属性,竟然这么屌!

    前段时间在做“高清壁纸推荐”小程序优化的时候,发现一个很实用的图片属性——能够实现最大化压缩图片大小.且图片质量最小的损失,在此之前一直没有注意.今天跟大家分享一下这个属性的用法,主要是让大家能够,意 ...

  2. [软件版本贴]SD.TEAM软件集

    手机轰炸机-版本:v1.0.下载地址:http://pan.baidu.com/1.zip! 营销软件盒-版本:v1.0.下载地址:http://pan.baidu.com/2.zip! 不加群提取群 ...

  3. [PHP学习教程 - 网络]003.获得当前访问的页面URL(Current Request URL)

    引言:获取当前请求的URL路径,自动判断协议(HTTP or HTTPS). 一句话的事情,下面直接上高清无MSK的精妙代码! 功能函数 获得当前请求的页面路径(URL)地址 语法:$url = ge ...

  4. python调用大漠插件教程01注册大漠

    使用大漠有两种方法,一种是直接调用特殊的dll实现不注册就能使(本人不会),另一种则是注册后使用. 如何用python注册大漠? from win32com.client import Dispatc ...

  5. 泛微 e-cology OA 前台SQL注入漏洞

    0x00概述 该漏洞是由于OA系统的WorkflowCenterTreeData接口在收到用户输入的时候未进行安全过滤,oracle数据库传入恶意SQL语句,导致SQL漏洞. 0x01影响范围 使用o ...

  6. 关于ueditor编译器

    取消自动保存提示.edui-editor-messageholder.edui-default{ visibility:hidden;} Qiyuwen 1033935470@qq.com

  7. Java面向对象中this关键字详解 意义+实例讲解【hot】

    this关键字 >>>便于理解简单的定义 this关键字可以简单的理解为,谁调用this所在的方法,this就是谁. 类的构造函数与getter.setter方法常用到this关键字 ...

  8. Java实现 LeetCode 692 前K个高频单词(map的应用)

    692. 前K个高频单词 给一非空的单词列表,返回前 k 个出现次数最多的单词. 返回的答案应该按单词出现频率由高到低排序.如果不同的单词有相同出现频率,按字母顺序排序. 示例 1: 输入: [&qu ...

  9. Java实现 LeetCode 657 机器人能否返回原点(暴力大法)

    657. 机器人能否返回原点 在二维平面上,有一个机器人从原点 (0, 0) 开始.给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束. 移动顺序由字符串表示.字符 move[i ...

  10. Java实现蓝桥杯模拟递增三元组

    问题描述 在数列 a[1], a[2], -, a[n] 中,如果对于下标 i, j, k 满足 0<i<j<k<n+1 且 a[i]<a[j]<a[k],则称 a ...