从今天开始我每周都会更新一篇文章,用于深度、详细、全方位的解释MySQL5.7源码
本人用的环境是MacOS 10.11.6,编译器用的是Xcode 7.3.1,mysql版本是5.7.12
既然是看源码,开始当然是找main方法,mysql服务端的启动入口位于mysqld/main.cc中,代码很简单,加上注释也不过十行
也就是说真正执行的就是这个mysqld_main这个函数,我们来看看他到底做了哪些事情,点进去之后会发现很多#ifndef _WIN32之类预编译指令,由于我们的电脑并非Windows,所以这些代码直接忽略,这个函数做的第一件事情当然是各种初始化工作,本节内容也就详细说一下这部分代码。
/*
main() for mysqld.
Calls mysqld_main() entry point exported by sql library.
*/
extern int mysqld_main(int argc, char **argv);
int main(int argc, char **argv)
{
return mysqld_main(argc, argv);
}
首先是预初始化performance_schema相关的一些全局变量,这些全局变量可以理解为是performance_schema里面的内存表
这里都是些重复的代码,就截取一部分好了,说到这就顺便简单介绍一下performance_schema这个新的数据库,这个库是一个确实存在的库,这个看mysql的存储数据的文件夹就能看出,其中的内存表由数据库自己维护,用于记录监控信息,不允许用户直接写。
他包括的表主要包含以下几类:
1.配置表,这些表是能手工修改的,用于配置管理的对象
既然都说到这里了就顺便说一下配置表的用法,配置表一共有5个,分别是
setup_actors:用于配置哪些主机、用户是否开启监控和监控内容是否储存,默认是全部主机全部用户都开启,值得一提的是这个表的role字断是个暂时没用的字段(修改后新连接的客户端)
setup_consumers:根据目标用于配置哪些事件会被监控,例如语句、事务 (修改后立刻生效)
setup_instruments:根据工具配置哪些事件会被收集,例如select功能,createtable功能 (修改后部分内容生效)
setup_objects:那些对象被监控,表、函数等 (修改后立刻生效)
setup_timers:监控使用的计时器种类 (修改后立刻生效)
2.瞬时表,就是那些当前监控数据的表,通常以current结尾
3.历史表,记录了历史的监控数据,通常以history和history_long结尾
4.按照一定维度的汇总表,通常是summary_by什么
5.实例表,记录了那些类型的对象实例被监控了,这里文档中写的是instance所以我翻译成实例,其实这个概念有一点绕,我解释一下,mysql的某一种监控会监控一些对象,这一组对应关系以及相关的信息就是一个实例
PFS_builtin_memory_class builtin_memory_program;
...
...
PFS_builtin_memory_class builtin_memory_scalable_buffer;
static void init_builtin_memory_class(PFS_builtin_memory_class *klass, const char* name)
{
klass->m_class.m_type= PFS_CLASS_MEMORY;
klass->m_class.m_enabled= true; /* Immutable */
klass->m_class.m_timed= false; /* Immutable */
klass->m_class.m_flags= PSI_FLAG_GLOBAL;
klass->m_class.m_event_name_index= 0;
strncpy(klass->m_class.m_name, name, sizeof(klass->m_class.m_name));
klass->m_class.m_name_length= strlen(name);
DBUG_ASSERT(klass->m_class.m_name_length < sizeof(klass->m_class.m_name));
klass->m_class.m_timer= NULL;
klass->m_stat.reset();
}
void init_all_builtin_memory_class()
{
init_builtin_memory_class( & builtin_memory_mutex,
"memory/performance_schema/mutex_instances");
init_builtin_memory_class( & builtin_memory_rwlock,
"memory/performance_schema/rwlock_instances");
...
...
}
给performance_schema预先定义的内存表分配好空间之后就到了my_init()方法了
这里面的主要的功能是初始化一些线程会用到的锁、信号量、变量等,其中封装了一些pthread的函数,这里简单列举说明
my_create_thread_local_key和my_get_thread_local
其实就是封装了pthread_key_create() 相当于java里面的ThreadLocal
pthread_getpecific和pthread_setspecific实现同一个线程中不同函数间共享数据的一种很好的方式。
pthread_mutex_init()是互斥锁的初始化
代码片段截取如下:
/**
Initialize my_sys functions, resources and variables
*/
my_bool my_init()
{
char *str;
if (my_init_done)
return FALSE;
my_init_done= TRUE;
my_umask= 0640; /* Default umask for new files */
my_umask_dir= 0750; /* Default umask for new directories */
/* Default creation of new files */
if ((str= getenv("UMASK")) != 0)
my_umask= (int) (atoi_octal(str) | 0640);
/* Default creation of new dir's */
if ((str= getenv("UMASK_DIR")) != 0)
my_umask_dir= (int) (atoi_octal(str) | 0750);
instrumented_stdin.m_file= stdin;
instrumented_stdin.m_psi= NULL; /* not yet instrumented */
mysql_stdin= & instrumented_stdin;
if (my_thread_global_init()) //这个函数用于初始化主线程变量和一些共享的全局变量
return TRUE;
if (my_thread_init()) //这个用于debug信息的初始化 可以忽略这个函数
return TRUE;
/* $HOME is needed early to parse configuration files located in ~/ */
if ((home_dir= getenv("HOME")) != 0)
home_dir= intern_filename(home_dir_buff, home_dir);
} /* my_init */
接着是加载默认参数,mysqld通过先加载默认参数,然后再将命令行上面的参数覆盖上去的办法获得合服期望的启动参数,这段逻辑比较简单这里就不解释了
接着是init_sql_statement_names();函数初始化statement的名字,statement的名字种类繁多,保存在一个全局数组中,这个函数用了很复杂的C语言技巧来实现了一个很简单的逻辑,道理在哪里我没看出来
static void init_sql_statement_names()
{
static LEX_CSTRING empty= { C_STRING_WITH_LEN("") };
//STATUS_VAR是一个结构体,里面有各种系统参数
//其中com_stat是STATUS_VAR结构体中的一个数组,用于保存所有statement类型的枚举值
//offsetof是一个宏,用于获取每个成员变量在对象中的偏移量(byte)
char *first_com= (char*) offsetof(STATUS_VAR, com_stat[0]);
char *last_com= (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_END]);
int record_size= (char*) offsetof(STATUS_VAR, com_stat[1])
- (char*) offsetof(STATUS_VAR, com_stat[0]);
char *ptr;
uint i;
uint com_index;
for (i= 0; i < ((uint) SQLCOM_END + 1); i++)
sql_statement_names[i]= empty;
SHOW_VAR *var= &com_status_vars[0];
while (var->name != NULL)
{
ptr= var->value;
if ((first_com <= ptr) && (ptr <= last_com))
{
com_index= ((int)(ptr - first_com))/record_size;
DBUG_ASSERT(com_index < (uint) SQLCOM_END);
sql_statement_names[com_index].str= var->name;
/* TODO: Change SHOW_VAR::name to a LEX_STRING, to avoid strlen() */
sql_statement_names[com_index].length= strlen(var->name);
}
var++;
}
sql_statement_names[(uint) SQLCOM_END].str= "error";
}
接着是sys_var_init()函数,听名字就知道应该是初始化系统变量的,代码片段如下
sys_var_chain all_sys_vars = { NULL, NULL }; //注意这个初始值并没有赋值
int sys_var_init()
{
my_hash_init(&system_variable_hash, system_charset_info, 100, 0,
0, (my_hash_get_key) get_sys_var_length, 0, HASH_UNIQUE,
PSI_INSTRUMENT_ME);
//其中system_variable_hash是一个用于保存sys_var的哈西表,这个是mysql自己实现的一种动态哈西表,这里先不展开谈
//system_charset_info是一个用于保存字符集相关信息的数据结构
//get_sys_var_length就是*length= var->name.length;return (uchar*) var->name.str;两句话的哈西函数
mysql_add_sys_var_chain(all_sys_vars.first);
//这个函数的下面说
}
/**
Add variables to the dynamic hash of system variables
*/
int mysql_add_sys_var_chain(sys_var *first)
{
sys_var *var;
for (var= first; var; var= var->next)
{
if (my_hash_insert(&system_variable_hash, (uchar*) var))
{
my_message_local(ERROR_LEVEL, "duplicate variable name '%s'!?",
var->name.str);
goto error;
}
}
...
}
mysql_add_sys_var_chain这个函数的本意应该是将all_sys_vars中的数据都加入system_variable_hash这个哈西表中,但是费解的是这个函数传入的看起来是个NULL,所以这段代码似乎是没有效果的,这个暂时搞不懂为什么,有知道为什么的同学欢迎私信我。
接着是调整一下几个参数,看方法名就知道是调整那几个参数,但是为什么要在代码里面动态调整呢,因为这些参数或是因为操作系统、或是互相影响,都会收到一些限制
void adjust_related_options(ulong *requested_open_files)
{
/* The order is critical here, because of dependencies. */
adjust_open_files_limit(requested_open_files);
adjust_max_connections(*requested_open_files);
adjust_table_cache_size(*requested_open_files);
adjust_table_def_size();
}
open_files_limit的限制:和table_open_cache和max_connections相关
//代码片段
/* MyISAM requires two file handles per table. */
limit_1= 10 + max_connections + table_cache_size * 2;
/*
We are trying to allocate no less than max_connections*5 file
handles (i.e. we are trying to set the limit so that they will
be available).
*/
limit_2= max_connections * 5;
/* Try to allocate no less than 5000 by default. */
limit_3= open_files_limit ? open_files_limit : 5000;
request_open_files= max
/* Notice: my_set_max_open_files() may return more than requested. */
effective_open_files= my_set_max_open_files(request_open_files);
open_files_limit= effective_open_files;
if (requested_open_files)
*requested_open_files= min
max_connections的限制:因为MySQL的最大连接数受到了安装平台的线程库、可用内存、每个连接需要使用的内存、每个连接的工作量、响应时间的影响,linux大概能承受500~1000并发请求,而Windows由于Posix通用接口层的限制,必须满足(open tables × 2 + open connections) < 2048
//代码片段:
max_connections= requested_open_files - 10 - TABLE_OPEN_CACHE_MIN * 2; //TABLE_OPEN_CACHE_MIN是写死的宏:400
TABLE_OPEN_CACHE_MIN
table_open_cache的限制:table_open_cache受到了max_connections的限制,至少需要连接数*该链接执行的join表的个数
//代码片段:
table_cache_size= max
TABLE_OPEN_CACHE_MIN);
table_cache_size_per_instance= table_cache_size / table_cache_instances;
table_definition_cache的限制:table_definition_cache是能放在缓存中的表的定义的数量,表的定义在缓存中的时候能够加快打开表的速度,默认是400 + (table_open_cache / 2)
//代码片段:
default_value= min
var= intern_find_sys_var(STRING_WITH_LEN("table_definition_cache"));
var->update_default(default_value);
这篇文章就先写到这