新建数据库¶
Conventions
- 每一个代码块的顶部都有它所属的文件(相对)路径,如果代码块属于某个函数(方法),那么顶部会有函数(方法)的声明。
- 代码块中的对象,如果很重要,会在行尾补充该对象的声明。
- 我使用的MySQL 版本是 8.0.41。你看到这篇时,可能有了更新的版本,比如 8.0.42,区别不大的。
校验¶
sql/conn_handler/connection_handler_per_thread.cc | |
---|---|
在连接阶段(上)篇中,我们讲过上面这个代码片段,它属于 static void *handle_connection(void *arg)
函数。
回顾
第 299 行 thd_prepare_connection 会验证用户名和密码,如果正确,那么返回 false,否则返回 true。
假设用户名密码正确,即 thd_prepare_connection 返回 false,程序会进入 else 分支。else 分支中是一个 while 循环,while 判断当连接存活时,调用 do_command 函数;连接不存活时,跳出循环,调用 end_connection 函数。
do_command 函数会阻塞等待客户端的下一条命令。
现在假设我们用户名和密码是正确的,那么此时服务端阻塞在第 303 行的 do_command
函数。
sql/sql_parse.cc | |
---|---|
具体来讲是阻塞在 do_command
函数中的上面这一行,这是在等待客户端发送新的命令。
当客户端发送新建数据库的请求时,即 create database test1;
,服务端被唤醒,继续往下执行。
sql/sql_parse.cc | |
---|---|
sql/sql_parse.cc | |
---|---|
sql/sql_parse.cc | |
---|---|
从 dispatch_command
中进入 dispatch_sql_command
函数,再进入 mysql_execute_command
函数。我们只需要知道这个调用链即可,接下来才是重点。
int mysql_execute_command(THD *thd, bool first_level) { // line 2946
// ignore
case SQLCOM_CREATE_DB: { // line 3859
const char *alias;
if (!(alias = thd->strmake(lex->name.str, lex->name.length)) ||
(check_and_convert_db_name(&lex->name, false) != // line 3862
Ident_name_check::OK))
break;
if (check_access(thd, CREATE_ACL, lex->name.str, nullptr, nullptr, true, // line 3865
false))
break;
/*
As mysql_create_db() may modify HA_CREATE_INFO structure passed to
it, we need to use a copy of LEX::create_info to make execution
prepared statement- safe.
*/
HA_CREATE_INFO create_info(*lex->create_info);
res = mysql_create_db( // line 3874
thd, (lower_case_table_names == 2 ? alias : lex->name.str),
&create_info);
break;
}
// ignore
}
由于 mysql_execute_command
函数很长,所以只把相关的代码片段贴了出来,其余的使用 // ingore
表示忽略。
mysql_execute_command
函数中有一个 switch 语句,针对不同的客户端语句,走不同的分支。上面的代码片段是新建数据库的分支,可以看到 case SQLCOM_CREATE_DB
,表明服务端已经解析了客户端发来的 SQL,知道这是一条新建数据库的 SQL。
第 3860 行 const char *alias;
声明 C 风格字符串变量 alias,第 3861 行从词法分析结果中取出数据库名字 lex->name.str
和长度 lex->name.length
,复制到自行管理的内存区域1,并将首地址赋值给 alias。现在 alias 就是数据库名字。
第 3862 行校验数据库名字长度有没有超过 192,最后一个字符是不是空白符。
第 3865 行校验用户有没有 CREATE_ACL
权限,即 mysql.user
表的 Create_priv
字段值为 Y。
第 3874 行真正创建数据库,接下来我们看 mysql_create_db
函数。
创建数据库¶
bool mysql_create_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) {
if (lock_schema_name(thd, lock_db_name)) return true; // line 377
dd::cache::Dictionary_client &dc = *thd->dd_client(); // line 379
dd::String_type schema_name{db}; // line 380
const dd::Schema *existing_schema = nullptr; // line 381
if (dc.acquire(schema_name, &existing_schema)) { // line 382
return true;
}
// ignore
size_t path_len = build_table_filename(path, sizeof(path) - 1, db, "", "", 0, //line 402
&was_truncated);
// ignore
path[path_len - 1] = 0; // Remove last '/' from path // line 408
// ignore
if (my_mkdir(path, 0777, MYF(0)) < 0) { // line 444
char errbuf[MYSQL_ERRMSG_SIZE];
my_error(ER_SCHEMA_DIR_CREATE_FAILED, MYF(0), db, my_errno(),
my_strerror(errbuf, MYSQL_ERRMSG_SIZE, my_errno()));
return true;
}
// ignore
if (store_in_dd) { // line 459
set_db_default_charset(thd, create_info);
if (dd::create_schema(thd, db, create_info->default_table_charset,
encrypt_schema)) {
// ignore
return true;
}
}
}
第 377 行获取锁,这样保证多个客户端同时创建同一个数据库时,只有一个会成功。
第 382 行从数据字典2中获取 test1
数据库名对应的 dd::Schema 类型的对象,并将指向对象的指针赋予 existing_schema。由于 test1
不存在,所以 existing_schema 是空指针。
第 402 行生成路径 ./test1/
。
第 403 行去掉路径中的最后一个斜线,得到 ./test1
。
第 444 行创建目录 ./test1
,此时可以查看我们服务器上的数据目录3有没有新建该目录。
第 462 行在数据字典中创建相应的对象,并写入磁盘。
小结¶
服务端在收到新建数据库的 SQL 后,校验了数据库名字的长度、最后一位是否是空白符、有无全局 create 权限,获取到锁以后,在数据目录下创建了同名目录,将数据库元数据写入数据字典的缓存和磁盘的系统表中。