首先来看tablespace的定义:

这里system tablespace是一个特殊的tablespace,他包含了很多数据文件(ibdata files).而且如果没有设置 file-per-table的话,所有的新创建的表的数据以及索引信息都会保存在它里面.

下面就是ibdata可能包含的内容

假设设置了file-per-table(默认打开),那么每一个表都会有自己的tablespace文件(.ibd).

还有一种就是temporary tablespace,也就是临时表的tablespace.

源码分析

既然我们已经知道tablespace是和table相关的,那么我们就按照这个逻辑来分析源码.

首先来看system tablespace的创建,在InnoDB中每一个tablespace都会有一个uint32类型的id,每一个唯一的id用来表示对应的tablespace,而system tablespace的space id就是0.

而由于只有system tablespace和temporary tablespace是共享的,因此他们在InnoDB中有专门的数据结构来表示他们(Tablespace表示所有的shared tablespace的基类).

  1. public:
  2. SysTablespace()
  3. : m_auto_extend_last_file(),
  4. m_last_file_size_max(),
  5. m_created_new_raw(),
  6. m_is_tablespace_full(false),
  7. m_sanity_checks_done(false) {
  8. /* No op */
  9. }

而对应的变量则是

  1. /** The control info of the system tablespace. */
  2. SysTablespace srv_sys_space;
  3. /** The control info of a temporary table shared tablespace. */
  4. SysTablespace srv_tmp_space;

tablespace数据文件的创建则是在SysTablespace::open_or_create中,因此我们来看这个函数.这个函数主要功能是遍历当前system tablespace的所有文件(m_files),然后存在就打开,不存在就创建对应的文件.

  1. files_t::iterator begin = m_files.begin();
  2. files_t::iterator end = m_files.end();
  3. ut_ad(begin->order() == 0);
  4. for (files_t::iterator it = begin; it != end; ++it) {
  5. if (it->m_exists) {
  6. err = open_file(*it);
  7. } else {
  8. err = create_file(*it);
  9. }

当打开文件之后,将会把打开的文件进行缓存(InnoDB会将所有的tablespace缓存在fil_system中).

fil_node_create就是将当前的文件加入到文件cache中,这是因为在InnoDB中所有的文件都会统一管理,包括redo/undo,所有的tablespace都会根据他们的spaceid进行缓存.

  1. char *fil_node_create(const char *name, page_no_t size, fil_space_t *space,
  2. bool is_raw, bool atomic_write, page_no_t max_pages) {
  3. auto shard = fil_system->shard_by_id(space->id);
  4. fil_node_t *file;
  5. file = shard->create_node(name, size, space, is_raw,
  6. IORequest::is_punch_hole_supported(), atomic_write,
  7. max_pages);
  8. return (file == nullptr ? nullptr : file->name);

此时的疑问就是m_files是何时被初始化的,也就是system tablespace文件名的初始化,这里system tablespace的文件名初始化是在数据库init的时候被初始化的,因此我们来看相关代码.

InnoDB中有一个变量叫做innobase_data_file_path,这个变量是一个字符串,这个字符串包含了所有的system tablespace需要创建 的文件以及一些属性,这个字符串默认值是在InnoDB引擎初始化的时候初始化的.

  1. /* Set default InnoDB temp data file size to 12 MB and let it be
  2. auto-extending. */
  3. innobase_data_file_path = (char *)"ibdata1:12M:autoextend";
  4. }

因此system tablespace创建文件也会根据这个配置来创建,也就是当InnoDB参数解析完毕之后进入system tablespace的文件创建.

  1. if (int error = innodb_init_params()) {
  2. DBUG_RETURN(error);
  3. }
  4. /* After this point, error handling has to use
  5. innodb_init_abort(). */
  6. if (!srv_sys_space.parse_params(innobase_data_file_path, true)) {
  7. ib::error(ER_IB_MSG_545)
  8. << "Unable to parse innodb_data_file_path=" << innobase_data_file_path;
  9. DBUG_RETURN(innodb_init_abort());
  10. }

parse_params这个函数将会解析innobase_data_file_path,然后根据不同的属性创建对应的文件,先来看参数解析.

然后第二步就是存储对应的文件名以及属性到m_files中,这里有一个重要的数据结构就是Datafile,每一个数据文件在InnoDB中的抽象就是Datafile.

  1. while (*ptr != '\0') {
  2. filepath = ptr;
  3. ptr = parse_file_name(ptr);
  4. if (*ptr == ':') {
  5. /* Make filepath a null-terminated string */
  6. *ptr = '\0';
  7. ptr++;
  8. }
  9. size = parse_units(ptr);
  10. ut_ad(size > 0);
  11. if (0 == strncmp(ptr, ":autoextend", (sizeof ":autoextend") - 1)) {
  12. m_auto_extend_last_file = true;
  13. ptr += (sizeof ":autoextend") - 1;
  14. if (0 == strncmp(ptr, ":max:", (sizeof ":max:") - 1)) {
  15. ptr += (sizeof ":max:") - 1;
  16. m_last_file_size_max = parse_units(ptr);
  17. }
  18. Datafile *datafile = &m_files.back();
  19. datafile->make_filepath(path(), filepath, NO_EXT);
  20. if (0 == strncmp(ptr, "new", (sizeof "new") - 1)) {
  21. ptr += (sizeof "new") - 1;
  22. }
  23. if (0 == strncmp(ptr, "raw", (sizeof "raw") - 1)) {
  24. ut_a(supports_raw);
  25. ptr += (sizeof "raw") - 1;
  26. /* Initialize new raw device only during initialize */
  27. m_files.back().m_type =
  28. #ifndef UNIV_HOTBACKUP
  29. opt_initialize ? SRV_NEW_RAW : SRV_OLD_RAW;
  30. #else /* !UNIV_HOTBACKUP */
  31. SRV_OLD_RAW;
  32. #endif /* !UNIV_HOTBACKUP */
  33. }
  34. if (*ptr == ';') {
  35. ++ptr;
  36. }
  37. order++;
  38. }

非共享的tablespace

然后我们来看非共享的tablespace的创建,一般来说每创建一个表都会创建一个新的ibd文件,也就是tablespace. 而这种tablespace以及Redolog/undolog都是属于fil_space_t这个结构体.通过上面的代码我们知道system tablespace最终也是创建一个fil_space_t然后再接入整个系统的tablespace的管理的.

这里先来看两个特殊的tablespace,也就是REDO log和UNDO log,由于这两个log也都是磁盘上的文件,因此在InnoDB中会讲这两个log文件作为一种特殊的tablespace,来看他们的初始化.

  1. static dberr_t create_log_files(char *logfilename, size_t dirnamelen, lsn_t lsn,
  2. char *&logfile0, lsn_t &checkpoint_lsn) {
  3. ........................
  4. /* Disable the doublewrite buffer for log files. */
  5. fil_space_t *log_space = fil_space_create(
  6. "innodb_redo_log", dict_sys_t::s_log_space_first_id,
  7. fsp_flags_set_page_size(0, univ_page_size), FIL_TYPE_LOG);
  8. .......................
  9. }

下面的undo log.

  1. static dberr_t srv_undo_tablespace_open(space_id_t space_id) {
  2. ....................................
  3. space = fil_space_create(undo_name, space_id, flags, FIL_TYPE_TABLESPACE);
  4. ..............

以后我们详细分析undolog/redolog的时候会再来分析这个地方

接下来我们就来看当一般表的创建的时候会发生什么(设置了file-per-table),对于一般的表,创建tablespace是跟随着ibd文件一起创建的(上面的都是在初始化的时候创建)。