前言
呵呵 最近同事有这样的一个需求
需要将 库1 的一张表 复制到 库2
然后 我想到了 之前一直使用的通过复制这个库的 data 文件来进行数据迁移的思路, 是需要复制这个 库对应的 data 目录下的数据文件, 以及 ibdata1 文件
然后 我又在想 这里的场景能否也使用这里的额方式呢 ?
我直接将 库1 的 $table.idb, $table.frm 复制到 库2, 然后 重启 mysql 服务器 是否可行??
然后 尝试了一下, 呵呵 直接 mysql 服务启动不起来了
而且 当时这个库还是有很多其他同事在使用, 因此 也是比较难受的
当时处理的方式是, 回滚了 $table.ibd, $table.frm 以及删除了 mysql-bin.index 的文件
当然 我目前还不知道 为什么这个文件 会对 mysql 服务启动造成影响, 呵呵 后面能够复现再回来探究吧
为什么 通过从 库1 复制 *.ibd 到 库2 导致 mysql 启动报错
日志信息如下, 不同的 mysql 版本错误信息可能会有所差异, 我这里复现的错误信息 和 出现问题的机器的错误信息就有所不同
我这里复现的处理方式是 删除 test02 中的 user.idb, user.frm, 并且将 test 中的 user.idb, user.frm 复制到 test02
2022-05-01 18:03:42 3782 [Note] InnoDB: The log sequence numbers 1745148 and 1745148 in ibdata files do not match the log sequence number 2805972 in the ib_logfiles!
2022-05-01 18:03:42 3782 [Note] InnoDB: Database was not shutdown normally!
2022-05-01 18:03:42 3782 [Note] InnoDB: Starting crash recovery.
2022-05-01 18:03:42 3782 [Note] InnoDB: Reading tablespace information from the .ibd files...
2022-05-01 18:06:23 3782 [ERROR] InnoDB: Attempted to open a previously opened tablespace. Previous tablespace test/user uses space ID: 6 at filepath: ./test/user.ibd. Cannot open tablespace test02/user which uses space ID: 6 at filepath: ./test02/user.ibd
2022-05-01 18:06:23 10ef305c0 InnoDB: Operating system error number 2 in a file operation.
InnoDB: The error means the system cannot find the path specified.
InnoDB: If you are installing InnoDB, remember that you must create
InnoDB: directories yourself, InnoDB does not create them.
InnoDB: Error: could not open single-table tablespace file ./test02/user.ibd
InnoDB: We do not continue the crash recovery, because the table may become
InnoDB: corrupt if we cannot apply the log records in the InnoDB log to it.
InnoDB: To fix the problem and start mysqld:
InnoDB: 1) If there is a permission problem in the file and mysqld cannot
InnoDB: open the file, you should modify the permissions.
InnoDB: 2) If the table is not needed, or you can restore it from a backup,
InnoDB: then you can remove the .ibd file, and InnoDB will do a normal
InnoDB: crash recovery and ignore that table.
InnoDB: 3) If the file system or the disk is broken, and you cannot remove
InnoDB: the .ibd file, you can set innodb_force_recovery > 0 in my.cnf
InnoDB: and force InnoDB to continue crash recovery here.
从日志中可以大致看出的是 test/user.idb 和 test02/user.idb 的 spaceId 都是 6, 然后 加载的时候出现了问题
然后 从逻辑上 spaceId 应该是一一映射到一个数据表文件的, mysql 校验的时候报错
按照 正常的理解, 应该是 删除掉 test02 下面的 user.idb, user.frm 或者 回滚 test02 下面的 user.idb, user.frm 就行
从 mysql 源码来看这个问题
报错的地方是在加载 单个表空间 的时候, 校验存在问题
根据 test02/user.ibd 的 fileSpace.id 来查询已有的 fileSpace 中是否存在 fileSpace, 发现已经存在一个 test/user.ibd 的 fileSpace, 然后验证不通过, 报错

fileSpace.id 是怎么加载?
从 idb 文件中加载 第一页[16k] 的数据, 然后读取 pageIdOffset 的数据作为 spaceId, 比如这里的 test02/user.ibd + 34[pageOffset] 为 6 作为 pageId

如下打开 test02/user.idb 找到偏移为 34 的连续四个字节, 0x00000006, spaceId 为 0x06

偏移为 38 的连续四个字节也是 spaceId
这部分数据称之为 spaceHeader, 下面的备注也写清楚了, 这个数据结构仅仅在第一页存储并使用

fileSpace 的维护?
上面校验的步骤, 我们看到了这个 fileSpace 的使用的地方, 根据 fileSpace.id 查询 fileSpace
那么 加载 fileSpace 之后, 将表空间添加到 fileSpace 的地方又在哪里呢?
将表空间添加到 fileSpace 的地方又在哪里呢?
是在加载表空间所有的验证, 准备工作都完成了之后, 将 space 注册到 fil_system.spaces 中

回溯问题
总结一下 就是 每一个数据表的 spaceId 期望应该是唯一的, 然后 硬盘上是存储在 对应的数据表文件的第一个 page 上面
然后 因为我们这里是直接将 库1 的 $table.idb 复制到了 库2, 这两个 $table.idb 文件的 spaceId 是相同的[因为是复制过来的]
然后导致了 mysql 加载 filelSpace 的时候出现了问题
查询数据表的 tabSpace 的数据, 可以通过 INNODB_SYS_TABLESPACE 进行查询
比如如下的 test/user 数据表的 spaceId 为 6, 和上面调试过程中得到的 spaceId 是一致的
然后 原有的 test02/user 数据表的 spaceId 为 24
我这里本地环境处理方式是 直接将 test02/user.* 回滚回去, 然后 重启数据库即可

完