跳转至

新建数据库

Conventions

  • 每一个代码块的顶部都有它所属的文件(相对)路径,如果代码块属于某个函数(方法),那么顶部会有函数(方法)的声明。
  • 代码块中的对象,如果很重要,会在行尾补充该对象的声明。
  • 我使用的MySQL 版本是 8.0.41。你看到这篇时,可能有了更新的版本,比如 8.0.42,区别不大的。

校验

sql/conn_handler/connection_handler_per_thread.cc
    if (thd_prepare_connection(thd))
      handler_manager->inc_aborted_connects();
    else {
      while (thd_connection_alive(thd)) {
        if (do_command(thd)) break;
      }
      end_connection(thd);
    }

在连接阶段(上)篇中,我们讲过上面这个代码片段,它属于 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
    rc = thd->get_protocol()->get_command(&com_data, &command);

具体来讲是阻塞在 do_command 函数中的上面这一行,这是在等待客户端发送新的命令。

当客户端发送新建数据库的请求时,即 create database test1;,服务端被唤醒,继续往下执行。

sql/sql_parse.cc
  return_value = dispatch_command(thd, &com_data, command);
sql/sql_parse.cc
      dispatch_sql_command(thd, &parser_state);
sql/sql_parse.cc
          error = mysql_execute_command(thd, true);

dispatch_command 中进入 dispatch_sql_command 函数,再进入 mysql_execute_command 函数。我们只需要知道这个调用链即可,接下来才是重点。

sql/sql_parse.cc
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 函数。

创建数据库

sql/sql_db.cc
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 权限,获取到锁以后,在数据目录下创建了同名目录,将数据库元数据写入数据字典的缓存和磁盘的系统表中。


  1. strmake 会调用 strmake_root,strmake_root 的第一个形式参数就是指向自行管理的内存区域。 

  2. 存放数据库、表、视图、触发器等数据库对象的元数据。 

  3. 系统变量 datadir 指向的路径,一般是 /var/lib/mysql。