转 MySQL源码分析

看到一个不错的介绍,原址如下:

http://software.intel.com/zh-cn/blogs/2010/08/20/mysql0/

 

MySQL源码分析(0):编译安装及调试

作者: Yuan Zhou (Intel) (7 篇文章) 日期: 八月 20, 2010 在 12:13 下午

编译安装
为了实现MySQL的更高级别的性能调优,我们通常需要理解其内部实现机制,并对其进行优化调试。在下面的系列中,我们会分别介绍MySQL的部分内部实现机制。

首先我们介绍如何从源代码部署一台MySQL服务器。

  1. 下载MySQL Community Server源码

    http://dev.mysql.com/downloads/ 本文中演示使用的是mysql-5.5.0-m2版本。

  2. 安装环境:

    [root@localhost ~]# uname -a
    Linux localhost.localdomain 2.6.18-164.el5 #1 SMP Tue Aug 18 15:51:48 EDT 2009 x86_64 x86_64 x86_64 GNU/Linux
    [root@localhost ~]# gcc -v
    ......
    Gcc-4.1.2 x86_64-redhat-linux
    Thread model: posix
    gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)

  3. 编译安装(以下命令全部以root身份运行)
    • 添加运行MySQL程序的mysql用户:

      groupadd mysql
      useradd mysql –g mysql

    • CFLAGS="-g –O0" CXX=gcc CXXFLAGS="-g –O0 -felide-constructors -fno-exceptions -fno-rtti" ./configure --prefix=Folder_you_want_to_install --with-extra-charsets=complex --enable-thread-safe-client --enable-local-infile  --enable-assembler --with-plugins=innobase --with-fast-mutexes
       建议使用-O0选项,即不使用gcc优化代码,方便于在调试时可以直接看到代码。
       --with-plugins=innobase,启用innodb存储引擎插件。
    • make && make install
      此命令结束后应该能在安装目录下看到MySQL的文件了。
  4. 配置

    配置MySQL的参数,可以support-files下配置文件的模板:
    cp support-files/my-medium.cnf /etc/my.cnf
    vi /etc/my.cnf

    按照测试的需求修改,注意加上datadir和innodb等部分重要参数。

    bin/mysql_install_db --user=mysql

    这里安装必要的数据库文件,如MySQL的系统表,--user=mysql是用来运行mysql的用户。

  5. 测试

    简单测试一下,运行mysqld_safe &
    在RHEL系统上以root运行mysqld_safe时,会自动切换到mysql用户来运行mysqld程序。

    查看mysqld进程,记录下启动时参数(斜体加粗部分),为今后的调试做准备。
    [root@localhost ~]# ps aux | grep mysql
    mysql    12261  0.0 13.0 1746120 200992 ?      Tl   Apr19   0:01 /usr/local/mysql/libexec/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/var --user=mysql --log-error=/usr/local/mysql/var/localhost.localdomain.err --pid-file=/usr/local/mysql/var/localhost.localdomain.pid --socket=/tmp/mysql.sock --port=3306
    root     12286  0.0  0.1 109396  2276 pts/3    S+   Apr19   0:00 mysql
    root     22624  0.0  0.0  61192   748 pts/6    S+   10:20   0:00 grep mysql


    mysqladmin –u root –p shutdown
    这条命令会关闭mysqld

调试程序
安装结束,进入正题调试环节。通常Linux下程序员倾向于使用强大的gdb(GNU Project Debugger)来调试程序,可以查看程序的内部结构、设置断点、查看调用堆栈等。在本文中,我们尝试使用kdbg,其本质上是带有图形界面的gdb,更方便直观,但依赖 KDE桌面环境。

  1. 安装

    yum install kdbg

  2. 启动kdbg并选择可执行程序libexec/mysqld。

    不同版本的kdbg在界面上可能不同。

  3. 在execution->arguments中输入前面记录mysqld的启动参数。

  4. 设置断点,按F5开始运行调试。

  5. 当程序运行到断点时会暂停,此时可以查看输出、调用栈和当前的堆栈值等。

参考资料
http://dev.mysql.com/doc/
http://forge.mysql.com/wiki/MySQL_Internals
http://www.kdbg.org/

MySQL源码分析(1):主要模块及数据流

作者: Yuan Zhou (Intel) (7 篇文章) 日期: 九月 1, 2010 在 11:49 上午

主要模块及数据流
经过多年的发展,mysql的主要模块已经稳定,基本不会有大的修改。本文将对MySQL的整体架构及重要目录进行讲述。

  1. 源码结构(MySQL-5.5.0-m2)
    • BUILD: 内含在各个平台、各种编译器下进行编译的脚本。如compile-pentium-debug表示在pentium架构上进行编译的脚本。
    • Client: 客户端工具,如mysql, mysqladmin之类。
    • Cmd-line-utils: readline, libedit工具。
    • Config: 给aclocal使用的配置文件。
    • Dbug: 提供一些调试用的宏定义。
    • Extra: 提供innochecksum,resolveip等额外的小工具。
    • Include: 包含的头文件
    • Libmysql: 库文件,生产libmysqlclient.so。
    • Libmysql_r: 线程安全的库文件,生成libmysqlclient_r.so。
    • Libservices: 5.5.0中新加的目录,实现了打印功能。
    • Man: 手册页。
    • Mysql-test: mysqld的测试工具一套。
    • Mysys: 为跨平台计,MySQL自己实现了一套常用的数据结构和算法,如string, hash等。
    • Netware: 在netware平台上进行编译时需要的工具和库。
    • Plugin: mysql以插件形式实现的部分功能。
    • Pstack: 异步栈追踪工具。
    • Regex: 正则表达式工具。
    • Scripts: 提供脚本工具,如mysql_install_db等
    • Sql: mysql主要代码,将会生成mysqld文件。
    • Sql-bench: 一些评测代码。
    • Sql-common: 存放部分服务器端和客户端都会用到的代码。
    • Storage: 存储引擎所在目录,如myisam, innodb, ndb等。
    • Strings: string库。
    • Support-files: my.cnf示例配置文件。
    • Tests: 测试文件所在目录。
    • Unittest: 单元测试。
    • Vio: virtual io系统,是对network io的封装。
    • Win: 给windows平台提供的编译环境。
    • Zip: zip库工具
  2. 主要数据结构
    • THD 线程描述符(sql/sql_class.h)

      包含处理用户请求时需要的相关数据,每个连接会有一个线程来处理,在一些高层函数中,此数据结构常被当作第一个参数传递。
      NET   net; // 客户连接描述符
      Protocol *protocol; // 当前的协议
      Protocol_text   protocol_text; // 普通协议
      Protocol_binary protocol_binary; // 二进制协议
      HASH    user_vars; //用户变量的hash值
      String  packet; // 网络IO时所用的缓存
      String  convert_buffer;               // 字符集转换所用的缓存
      struct  sockaddr_in remote; //客户端socket地址

      THR_LOCK_INFO lock_info; // 当前线程的锁信息
      THR_LOCK_OWNER main_lock_id; // 在旧版的查询中使用
      THR_LOCK_OWNER *lock_id; //若非main_lock_id, 指向游标的lock_id
      pthread_mutex_t LOCK_thd_data; //thd的mutex锁,保护THD数据(thd->query, thd->query_length)不会被其余线程访问到。

      Statement_map stmt_map; //prepared statements和stored routines 会被重复利用
      int insert(THD *thd, Statement *statement); // statement的hash容器
      class Statement::
      LEX_STRING name; /* prepared statements的名字 */
      LEX *lex; //语法树描述符
      bool set_db(const char *new_db, size_t new_db_len)
      void set_query(char *query_arg, uint32 query_length_arg);
      {
      pthread_mutex_lock(&LOCK_thd_data);
      set_query_inner(query_arg, query_length_arg);
      pthread_mutex_unlock(&LOCK_thd_data);
      }

    • NET 网络连接描述符(sql/mysql_com.h)

      网络连接描述符,对内部数据包进行了封装,是client和server之间的通信协议。

      Vio *vio; //底层的网络I/O socket描述符
      unsigned char *buff,*buff_end,*write_pos,*read_pos; //缓存相关
      unsigned long remain_in_buf,length, buf_length, where_b;
      unsigned long max_packet,max_packet_size; //当前值;最大值
      unsigned int pkt_nr,compress_pkt_nr; //当前(未)压缩包的顺序值
      my_bool compress; //是否压缩
      unsigned int write_timeout, read_timeout, retry_count; //最大等待时间
      unsigned int *return_status; //thd中的服务器状态
      unsigned char reading_or_writing;
      /*1 代表读, 2 代表写, 0代表无状态 */
      unsigned int last_errno; //返回给客户端的错误号
      unsigned char error;
      /*0:执行成功
      1:在协议层有逻辑错误
      2:系统调用或标准库出错
      3:特例,表示缓存不能装下当前这么大的包
      */

    • TABLE 数据库表描述符(sql/table.h)

      数据库表描述符,分成TABLE和TABLE_SHARE两部分。
      handler *file; //指向这张表在storage engine中的handler的指针
      THD *in_use;  /* 使用这张表的thread号 */
      Field **field; /* 指向数据域的指针*/
      uchar *record[2]; /* 指向记录的指针*/
      uchar *write_row_record; /* 在THD::write_row中用来做优化 */
      uchar *insert_values;                  /* INSERT ... UPDATE语句用 */
      /*
      可用来直接获取表中数据而不用读取行的当前key的映射值
      */
      key_map covering_keys;
      key_map quick_keys, merge_keys;
      key_map keys_in_use_for_query;
      /* 可用以生成GROUP BY结果的key映射值 */
      key_map keys_in_use_for_group_by;
      /* 可用以生成ORDER BY 结果的key映射值 */
      key_map keys_in_use_for_order_by;
      KEY  *key_info; /* 数据库中key的信息*/

      HASH name_hash; //数据域名字的hash值
      MEM_ROOT mem_root; //内存块
      LEX_STRING db;
      LEX_STRING table_name;
      LEX_STRING table_cache_key;
      enum db_type db_type //当前表的storage engine类型
      enum row_type row_type //当前记录是定长还是变长
      uint primary_key;
      uint next_number_index; //自动增长key的值
      bool is_view ;
      bool crashed;

    • FIELD 字段描述符(sql/field.h)

      域描述符,是各种字段的抽象基类。
      uchar *ptr; // 记录中数据域的位置
      uchar *null_ptr; // 记录 null_bit 位置的byte
      TABLE *table;           // 指向表的指针
      TABLE *orig_table;       // 指向原表的指针
      const char **table_name, *field_name;
      LEX_STRING comment;
      /* 数据域是下列key的一部分 */
      key_map key_start, part_of_key, part_of_key_not_clustered;
      key_map       part_of_sortkey;
      /*各种数据域的类型*/
      enum utype  { NONE,DATE,SHIELD,NOEMPTY,CASEUP,PNR,BGNR,PGNR,YES,NO,REL,
      CHECK,EMPTY,UNKNOWN_FIELD,CASEDN,NEXT_NUMBER,INTERVAL_FIELD,
      BIT_FIELD, TIMESTAMP_OLD_FIELD, CAPITALIZE, BLOB_FIELD,
      TIMESTAMP_DN_FIELD, TIMESTAMP_UN_FIELD, TIMESTAMP_DNUN_FIELD};
      …..
      virtual int  store(const char *to, uint length,CHARSET_INFO *cs)=0;
      inline String *val_str(String *str) { return val_str(str, str); }

    • Utility API Calls 各种API

      各种核心的工具,例如内存分配,字符串操作或文件管理。标准C库中的函数只使用了很少一部分,C++中的函数基本没用。
      void *my_malloc(size_t size, myf my_flags) //对malloc的封装
      size_t my_write(File Filedes, const uchar *Buffer, size_t Count, myf MyFlags) //对write的封装

    • Preprocessor Macros 处理器宏

      Mysql中使用了大量的C预编译,随编译参数的不同最终代码也不同。
      #define max(a, b) ((a) > (b) ? (a) : (b)) //得出两数中的大者

      do \
      { \
      char compile_time_assert[(X) ? 1 : -1] \
      __attribute__ ((unused)); \
      } while(0)
      使用gcc的attribute属性指导编译器行为
      • Global variables 全局变量
      • configuration settings
      • server status information
      • various data structures shared among threads
      主要包括一些全局的设置,服务器信息和部分线程间共享的数据结构。
      struct system_status_var global_status_var; //全局的状态信息
      struct system_variables global_system_variables; //全局系统变量

  3. 主要调用流程
    1. MySQL启动

      主要代码在sql/mysqld.cc中,精简后的代码如下:
      int main(int argc, char **argv) //标准入口函数
      MY_INIT(argv[0]); //调用mysys/My_init.c->my_init(),初始化mysql内部的系统库
      logger.init_base(); //初始化日志功能
      init_common_variables(MYSQL_CONFIG_NAME,argc, argv, load_default_groups) //调用load_defaults(conf_file_name, groups, &argc, &argv),读取配置信息
      user_info= check_user(mysqld_user);//检测启动时的用户选项
      set_user(mysqld_user, user_info);//设置以该用户运行
      init_server_components();//初始化内部的一些组件,如table_cache, query_cache等。
      network_init();//初始化网络模块,创建socket监听
      start_signal_handler();// 创建pid文件
      mysql_rm_tmp_tables() || acl_init(opt_noacl)//删除tmp_table并初始化数据库级别的权限。
      init_status_vars(); // 初始化mysql中的status变量
      start_handle_manager();//创建manager线程
      handle_connections_sockets();//主要处理函数,处理新的连接并创建新的线程处理之

    2. 监听接收链接

      主要代码在sql/mysqld.cc中,精简后的代码如下:
      THD *thd;
      FD_SET(ip_sock,&clientFDs); //客户端socket
      while (!abort_loop)
      readFDs=clientFDs;
      if (select((int) max_used_connection,&readFDs,0,0,0) error && net->vio != 0 &&
      !(thd->killed == THD::KILL_CONNECTION))
      {
      if(do_command(thd)) //处理客户端发出的命令
      break;
      }
      end_connection(thd);
      }

    3. 预处理连接

      thread_count++;//增加当前连接的线程
      thread_scheduler.add_connection(thd);
      for (;;) {
      lex_start(thd);
      login_connection(thd);          // 认证
      prepare_new_connection_state(thd); //初始化thd描述符
      while(!net->error && net->vio != 0 &&
      !(thd->killed == THD::KILL_CONNECTION))
      {
      if(do_command(thd)) //处理客户端发出的命令
      break;
      }
      end_connection(thd);
      }

    4. 处理

      do_command在sql/sql_parse.cc中:读取客户端传递的命令并分发。
      NET *net= &thd->net;
      packet_length= my_net_read(net);
      packet= (char*) net->read_pos;
      command= (enum enum_server_command) (uchar) packet[0]; //从net结构中获取命令
      dispatch_command(command, thd, packet+1, (uint) (packet_length-1));//分发命令
      在dispatch_command函数中,根据命令的类型进行分发。
      thd->command=command;
      switch( command ) {
      case COM_INIT_DB: ...;
      case COM_TABLE_DUMP: ...;
      case COM_CHANGE_USER: ...;
      ….
      case COM_QUERY: //如果是查询语句
      {
      alloc_query(thd, packet, packet_length)//thd->set_query(query, packet_length);
      mysql_parse(thd, thd->query(), thd->query_length(), &end_of_stmt);
      // 解析查询语句
      ….
      }

      在mysql_parse函数中,
      lex_start(thd);
      if (query_cache_send_result_to_client(thd, (char*) inBuf, length) sql_command

      在mysql_execute_command中,根据命令类型,转到相应的执行函数。
      switch (lex->sql_command) {
      LEX  *lex= thd->lex;
      TABLE_LIST *all_tables;
      case SQLCOM_SELECT:
      check_table_access(thd, lex->exchange ? SELECT_ACL | FILE_ACL :  SELECT_ACL,  all_tables, UINT_MAX, FALSE);     //检查用户权限
      execute_sqlcom_select(thd, all_tables); //执行select命令
      break;

      case SQLCOM_INSERT:
      { res= insert_precheck(thd, all_tables) //rights
      mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
      lex->update_list, lex->value_list,
      lex->duplicates, lex->ignore);
      break;
      在execute_sqlcom_select函数中,
      res= open_and_lock_tables(thd, all_tables)//directly and indirectly
      res= handle_select(thd, lex, result, 0);

      handle_select在sql_select.cc中,调用mysql_select ,在mysql_select中,
      join->prepare();//Prepare of whole select (including sub queries in future).
      join->optimize();//global select optimisation.
      join->exec();//

      在mysql_insert函数中,
      open_and_lock_tables(thd, table_list)
      mysql_prepare_insert(); //prepare item in INSERT statment
      while ((values= its++))
      write_record(thd, table ,&info);//写入新的数据

      在write_record函数中,
      table->file->ha_write_row(table->record[0])
      ha_write_row在Handler.cc中,只是一个接口
      write_row(buf); //调用表存储所用的引擎

    当客户端链接上mysql服务端时,系统为其分配一个链接描述符thd,用以描述客户端的所有信息,将作为参数在各个模块之间传递。一个典型的客户端查询在MySQL的主要模块之间的调用关系如下图所示:

    当mysql启动完毕后,调用handle_connection_sockets等待客户端连接。当客户端连接上服务器时,服务处理函数将接受连接,会为其创建链接线程,并进行认证。如认证通过,每个连接线程将会被分配到一个线程描述符thd,可能是新创建的,也可能是从cached_thread线程池中复用的。该描述符包含了客户端输入的所有信息,如查询语句等。服务器端会层层解析命令,根据命令类型的不同,转到相应的sql执行函数,进而给传递给下层的存储引擎模块,处理磁盘上的数据库文件,最后将结果返回。执行完毕后thd将被加入cached_thread中。

 

 

MySQL源码分析(2):Mysql中的内存分配相关

作者: Yuan Zhou (Intel) (7 篇文章) 日期: 九月 1, 2010 在 11:50 上午

Mysql中的内存分配相关

  1. 涉及到内存的配置参数
    1. 这些参数可以分成两部分,分别对应MySQL中的两个层次:服务器层和存储引擎层。

  2. MySQL服务器相关:

    每个连接到MySQL服务器的线程都需要有自己的缓冲,默认为其分配256K。事务开始之后,则需要增加更多的空间。运行较小的查询可能仅给指定的线程增加少量的内存消耗,例如存储查询语句的空间等。但如果对数据表做复杂的操作比较复杂,例如排序则需要使用临时表,此时会分配大约read_buffer_size,sort_buffer_size,read_rnd_buffer_size,tmp_table_size大小的内存空间。不过它们只是在需要的时候才分配,并且在那些操作做完之后就释放了。

    read_buffer_size是MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。read_buffer_size变量控制这一缓冲区的大小。如果对表的顺序扫描请求非常频繁,并且你认为频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能。

    sort_buffer_size是MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小。该变量会监测sort_merge_passed, sort_range, sort_rows, sort_scan的状况。通常较小的sort_merge_passed性能越高,但是也与workload的特性有关。

    read_rnd_buffer_size是MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
    query_cache_size是MySql的查询缓冲大小。(从4.0.1开始,MySQL提供了查询缓冲机制)使用查询缓冲,MySQL将SELECT语句和查询结果存放在缓冲区中,今后对于同样的SELECT语句(区分大小写),将直接从缓冲区中读取结果。根据MySQL用户手册,使用查询缓冲最多可以达到238%的效率。

    此外还有每个连接中会使用的一些变量会消耗少量内存。

  3. MyISAM引擎相关

    key_buffer_size存储了所有index的缓存,一般我们设为16M,通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理。比例key_reads / key_read_requests应该尽可能的低,至少是1:100,1:1000更好(上述状态值可以使用'key_read%'获得用来显示状态数据)。key_buffer_size只对MyISAM表起作用。即使不使用MyISAM存储引擎,但是内部的临时磁盘表是MyISAM表,故也要使用该值。

  4. InnoDB引擎相关

    innodb_buffer_pool_size对于InnoDB表来说,作用就相当于key_buffer_size对于MyISAM表的作用一样。InnoDB使用该参数指定大小的内存来缓冲数据和索引。对于单独的MySQL数据库服务器,手册上推荐把该值设置成物理内存的80%。
    innodb_additional_mem_pool_size指定InnoDB用来存储数据字典和其他内部数据结构的内存池大小。缺省值是1M。通常不用太大,只要够用就行,应该与表结构的复杂度有关系。如果不够用,MySQL会在错误日志中写入一条警告信息。
    innodb_log_buffer_size指定InnoDB用来存储日志数据的缓存大小,如果您的表操作中包含大量并发事务(或大规模事务),并且在事务提交前要求记录日志文件,请尽量调高此项值,以提高日志效率。

Key_buffer_size

Table_open_cache

Sort_buffer_size

Read_buffer_size

Net_buffer_length

Read_rnd_buffer_size

Innodb_buffer_pool_size

Innodb_additional_mem_pool_size

Innodb_log_buffer_size

Query_cache_size Mysql中最小内存占用公式

key buffer

innodb buffer pool

innodb log buffer

innodb additional mempool

net buffer

sort buffer

myisam sort buffer

read buffer

join buffer

readrnd buffer

minmemoryneeded = globalbuffers + (threadbuffers * max_connections)

其中globalbuffers 包括:

threadbuffers 包括:

参考资料
http://dev.mysql.com/doc/refman/5.0/en/memory-use.html
http://www.mysqlperformanceblog.com/2006/05/17/mysql-server-memory-usage/

MySQL源码分析(3):配置文件详解

作者: Yuan Zhou (Intel) (7 篇文章) 日期: 九月 1, 2010 在 11:51 上午

配置文件详解

  1. 关于配置文件存放位置和优先级

    在一次部署中,我们发现MySQL没有按照配置文件中的innodb_data_path选项创建文件,查找后发现这台机器上有多个配置文件:/etc/my.cnf和/etc/mysql/my.cnf,而MySQL会优先读取/etc/mysql/my.cnf。当出现多个配置文件时,其优先级是如何确定的?源码面前,了无秘密。

    用kDbg跟踪调试发现MySQL对系统中各处配置文件的优先级处理如下:
    mysys/default.c
    406行: int load_defaults(const char *conf_file, const char **groups,
    int *argc, char ***argv)

    这里其实只是包了一层,调用另一个函数。
    450行: int my_load_defaults(const char *conf_file, const char **groups,
    int *argc, char ***argv, const char ***default_directories)

    函数中段调用init_default_directories,获得程序初始化时将会尝试读取配置文件的所有目录,共五个,依次为:
    1. /etc
    2. /etc/mysql
    3. $BASEDIR/mysql
    4. cur_dir
    5. ~/

    在506行调用了一个函数my_search_option_files,其定义为:
    int my_search_option_files(const char *conf_file, int *argc, char ***argv,
    uint *args_used, Process_option_func func,
    void *func_ctx, const char **default_directories)

    此函数的第259行
    for (dirs= default_directories ; *dirs; dirs++)
    配置文件优先级在此体现,即使前面的配置文件中设置了选项,也会被后面配置中设定的值覆盖。例如在/etc/my.cnf中设定key_bufer_size=200M,但在/etc/mysql/my.cnf中又设定了key_buffer_size=300M,那么最终MySQL启动时将以后者为准。

  2. 几个重要的配置参数
    • --datadir: 数据文件的存放路径。
    • --basedir: mysql: 安装路径。
    • --concurrent_insert: 默认情况下(AUTO)mysql允许对MyISAM引擎的select和insert操作并行执行。
    • --key_buffer_size: 指定缓存key index用的cache大小。
    • --table_open_cache: 所有threads打开的table数量。
    • --sort_buffer_size: 每个线程都需要分配一个排序缓存,用于ORDER BY或GROUP BY。
    • --read_buffer_size: 每个线程对表做scan操作时需要分配这样的缓存,需要是4k的倍数。
    • --read_rnd_buffer_size: Sort_buffer中的数据被按照Key排序,此时MySQL会从中取出一部分数据按照物理磁盘上行指针的顺序排列并放入read_rnd_buffer中。
    • --thread_cache_size: 服务端可以重新使用的线程数。当一个客户端连接线程结束工作时,会被放入该缓存中以备重用。
    • --tmp_table_size: 可以使用的最大内存中的临时表。
    • --query_cache_size: 缓存查询结果的内存使用量,默认为禁止(0)

    在mysqld.cc中定义了所有可配置的服务端参数:
    enum options_mysqld
    {
    OPT_ISAM_LOG=256,            OPT_SKIP_NEW,
    OPT_SKIP_GRANT,              OPT_SKIP_LOCK,
    OPT_ENABLE_LOCK,             OPT_USE_LOCKING,
    . .
    . .
    . .
    OPT_SYNC_RELAY_LOG_INFO,
    OPT_SYNC_MASTER_INFO
    }

    当MySQL启动时,会接受命令行和配置文件中的参数值。
    if (init_common_variables(MYSQL_CONFIG_NAME, argc, argv, load_default_groups))
    unireg_abort(1);

    init_common_variables会调用my_load_defaults,该函数会从配置文件中读取参数值并将其放到通过命令行参数传递进来的值的前面,从而使得命令行参数可以覆盖配置文件中的参数。
    接着会调用get_options()从合并后的argc, argv[]中解析出参数值,最后程序调用libmysql/my_getopt.c中的handle_options,此函数会跟stuct my_option中的参数比较,如果有模糊或错误的参数则返回错误,否则调用mysqld_get_one_option()处理每一个参数。

MySQL源码分析(4):InnoDB主要数据结构及调用流程

作者: Yuan Zhou (Intel) (7 篇文章) 日期: 九月 1, 2010 在 11:51 上午

InnoDB主要数据结构及调用流程

  1. InnoDB是MySQL中常用的数据引擎。本文将从源码级别对InnoDB重点数据结构和调用流程进行分析。

主要数据结构(buf0buf.h)

Buf_pool

Buf_pool是整个buffer系统中核心数据结构,数据库中所有的操作都会在这缓冲层得到体现。我们可以在配置文件中(InnoDB_buffer_pool_size)指定该缓冲池的大小。
Buffer pool中又包含了多层数据结构:为了实现对buffer_pool的在线大小调整,引入了chunk数据结构;

Chunk


在chunk数据结构是更具体的内存缓存,主要包含控制内存块的block结构。
 Block

Block主要的数据结构是page和frame,这两个结构是用来存储硬盘上的数据库page的。当硬盘存储未使用压缩时,数据会被读取到frame中;当使用压缩后,会存储到page.zip中。
Page结构必须要放在该结构体的第一个位置,方便以后使用指针时可以方便的在page和block间进行转换。
例如:(buf_page_t *) buf_block_t *p
可以得到一个类型为buf_page的指针

Page

Page结构主要是存储硬盘上的文件,通过space和offset唯一对应到硬盘上的数据库文件;io_fix指出该page的类型(读或写);Zip存储压缩过的page。

Buf_pool示意图


Io_threads

在InnoDB内部,使用默认配置参数时,共有10个线程,其中:
4个read线程,4个write线程,负责进行异步的读写操作。
1个log write线程,负责将操作记录进日志文件
1个srv_master线程,负责定时写数据到磁盘等工作。
在InnoDB插件初始化时,会调用innobase_start_or_create_for_mysql,此函数将完成InnoDB存储引擎的初始化工作。创建io threads的部分在函数是
os_thread_create(io_handler_thread, n + i, thread_ids + i)其中io_handler_thread是回调函数,主要是为每个后台线程分配任务
for (i = 0;; i++) {
fil_aio_wait(segment);

mutex_enter(&ios_mutex);
ios++;
mutex_exit(&ios_mutex);
}
fil_aio_wait中起主要作用的是os_aio_simulated_handle,该函数根据线程在InnoDB中的编号进行模拟的异步IO设置。
segment = os_aio_get_array_and_local_segment(&array, global_segment);

Aio 与 sync aio
在mysql诞生时,Linux上还未出现AIO机制,于是InnoDB自己实现了一套异步io的框架。
关于Linux aio的介绍,AIO first entered the Linux kernel in 2.5 and is now a standard feature of 2.6 production kernels. http://www.ibm.com/developerworks/linux/library/l-async/

最近Innobase在innodb plugin中实现了真正的AIO机制。http://blogs.innodb.com/wp/2010/04/innodb-performance-aio-linux/ 我们会在以后的时间来介绍他。

Arrays

os_aio_read_array //异步读

os_aio_write_array //异步写

os_aio_ibuf_array //insert bufer

os_aio_log_array //日志array

os_aio_sync_array //同步io array

在InnoDB中引入了sync aio,目标是为了在代码级别上与aio的形式保持一致。

为了实现InnoDB的异步IO所引入的数组,系统中一共存在五类数组:

当出现IO请求时,异步机制会将此请求插入其中一个插槽(slot),并等待执行请求的信号;IO完毕,从该插槽中删除。

Segment

在array中,segment被用作array的标识符,根据segment值可以得到array的种类。例如在默认的array数量下(4个read,4个write)3号表示read array

Slot

存放异步读写请求的插槽。

调用流程

在InnoDB中,为了提高响应速度,有时需要立即返回数据,此时采用同步的读机制;而另一些对速度要求不是很高的情况下,例如预读取,InnoDB会采用异步读的方式来加载数据到内存。而在开启double write buffer写数据的情况下,InnoDB会先将数据写一遍到硬盘上某一块称之为double write buffer的区域,然后再在将来某时写到磁盘上,此时会根据系统的当前负载、当前内存中脏的页数有不同的延迟。

异步读写时等待请求:

当缓存块/文件读写完毕时,发出信号:
fil_io() -> os_aio()->
os_aio_simulated_wake_handler_threads()->os_aio_simulated_wake_handler_thread->
os_event_set()->pthread_cond_broadcast()

MySQL源码分析(5):Innodb缓存系统

作者: Yuan Zhou (Intel) (7 篇文章) 日期: 九月 1, 2010 在 11:52 上午

Innodb缓存系统

  1. buffer操作
    1. 根据tbl_test文件的space id和offset检查buffer pool中是否已经有缓存,若有则直接读取该page信息,并返回结果;
    2. 若没有缓存则会先分配一个空白的page,然后读取磁盘上的数据加载进内存,并将结果返回。

    当Sql级别命令执行到Innodb的buffer层时就对应到在buf_pool上的对buffer block的读写等操作。
    例如客户端输入以下查询语句:
    select * from tbl_test;
    层层解析执行至缓存系统中将产生如下动作:

  2. Innodb buffer pool 系统简介

  3. buf_pool->free, 空闲块链表

  4. buf_pool->flush_list, 待写块链表

  5. buf_pool->LRU, LRU块链表

  6. buf_pool->unzip_LRU, 解压后的LRU块链表

  7. buf_pool->zip_clean, 干净的压缩块链表

  8. buf_pool->zip_free[BUF_BUDDY_SIZE],空闲的压缩块链表

buf0buf.c 是整个buffer系统的核心部分,提供了磁盘文件与内存交互的接口。整个buffer pool主要通过这几个链表来进行组织。

Buf0lru.c是buffer系统中负责缓存替换的算法,主要操作对象是LRU链表,目的是为了更好的使用缓存。
Buf0flu.c是系统中负责写数据到磁盘的部分。为了节省系统的IO开销,Innodb实现该部分时,还考虑当前系统中脏的页数、当前系统的负载等因素。
Buf0rea.c是系统中负责读数据的部分,包括普通的读、预读取、insert buffer、恢复。
Buf0buddy.c二进制伙伴分配算法,在分配压缩过的内存块时使用,避免系统中出现内存碎片。
此外,还包含了mem0pool.c来处理一些底层的数据。

读操作(从磁盘上加载数据到内存中)

当数据库文件未使用压缩格式存储时,innodb直接通过buf_LRU_get_free_block获得一个空闲的控制块。而当MySQL配置了压缩格式存储时,系统会通过二进制伙伴算法分配得到一个大小合适的控制块,防止出现内存碎片。主要调用流程如下:

Buf_page_get_gen是InnoDB缓存系统封装给外部系统调用的一个主要API。该函数用途是获得一个包含数据的page,并返回给调用函数。在实际使用时调用栈如下图所示:

写操作(从buffer pool中删除page并flush到磁盘上)

入口函数是buf_page_release,用以释放buffer pool中的page,该函数会把脏的数据标记到flush队列中。

在Innodb中有一个主控线程(srv_master_thread),主要负责定时写数据到磁盘文件上,其刷新频率的理论值为1秒或10秒,具体参照服务器当前的负载及buffer pool中脏页的比例动态调整,其主要的代码调用过程如下:

 

 

MySQL源码分析(6):Innodb文件格式及压缩配置

作者: Yuan Zhou (Intel) (7 篇文章) 日期: 八月 28, 2010 在 6:23 下午

Innodb文件格式及压缩配置
Innodb文件格式代表了数据库文件在磁盘上的组织形式。

Tablespace包含了很多文件或raw disk partition。
当innodb_file_per_table = on时,每个数据库会使用单独的ibd文件存储数据,否则将和系统表等数据一起放在innodb_data_file_path指定的文件中。
在系统表空间中,innodb会维护一些系统信息:

Internal data dictionary

Undo

Insert buffer

Double write buffer

Mysql replication info

表空间有多个层次,其中最小的单位是page,在未压缩时每个page是16k;64个page组成一个extent;多个extent和page构成一个segment。

在Innodb中,page有很多中格式,如下图所示:

存放若干条记录的page的格式如下图所示,由page header,page trailer,page body组成。

在Innodb中可以指定文件格式,它们会是Antelope, Barracuda, Cheetah, Dragon, Elk, Fox等等,按照字母顺序继续排列。
Antelope是Built-in-InnoDB(MySQL内置的InnoDB)支持文件格式的代号,有两种“数据表格式”(row_format):Redundant、Compact;Barracuda是InnoDB Plugin支持的文件格式,在原来的基础上新增了两种数据表格式的支持:Dynamic和Compressed。
一般,innodb_file_format在配置文件中指定;row_format则在创建数据表时指定。

启用压缩格式的条件
a) Innodb_file_per_table = 1
b) Innodb_file_format = barracuda
CREATE TABLE name (column1 INT PRIMARY KEY) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
这条语句将会创建一个压缩的page size=8K的表。

参考文献:
1. http://dev.mysql.com/doc/refman/5.0/en/innodb-file-space.html
2. InnoDB Internals: InnoDB File Formats and Source Code Structure,MySQL Conference, April 2009, Heikki Tuuri CEO Innobase, Calvin Sun Principal Engineer, Oracle Corporation

 

你可能感兴趣的:(MySQL)