以下分析及实验基于MySQL5.6.12
用法:
简单的实验了一把,使用sysbench创建的表
mysql> FLUSH TABLES sbtest1 FOR EXPORT;
Query OK, 0 rows affected (0.00 sec)
观察到sb库下产生的新文件:
$ ls -lh sbtest1.*
-rw-rw—- 1 mysql dba 563 Jun 10 10:29 sbtest1.cfg
-rw-rw—- 1 mysql dba 8.5K Jun 7 11:29 sbtest1.frm
-rw-rw—- 1 mysql dba 276M Jun 9 00:49 sbtest1.ibd
.cfg包含了Innodb的数据词典信息,例如有多少列,数据类型及顺序是什么,以及包含哪些索引等等。
将ibd和cfg文件拷贝后,执行unlock tables,注意unlock操作会移除cfg文件
目标库上,建立对应的空表:
mysql> CREATE TABLE `sbtest1` (
-> `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
-> `k` int(10) unsigned NOT NULL DEFAULT ‘0’,
-> `c` char(120) NOT NULL DEFAULT ”,
-> `pad` char(60) NOT NULL DEFAULT ”,
-> PRIMARY KEY (`id`),
-> KEY `k` (`k`)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.09 sec)
mysql> alter table sbtest1 discard tablespace;
Query OK, 0 rows affected (0.04 sec)
将sbtest1.cfg和sbtest1.ibd拷贝到这实例数据目录上面,修改好对应的权限,然后执行。
mysql> alter table sbtest1 import tablespace;
Query OK, 0 rows affected (3.49 sec)
mysql> select count(*) from sbtest1;
+———-+
| count(*) |
+———-+
| 1000000 |
+———-+
1 row in set (1.01 sec)
不过其实cfg文件也不是必须要和ibd文件一一对应
同样的,重新创建表,只拷贝ibd文件:
mysql> alter table sbtest1 import tablespace;
Query OK, 0 rows affected, 1 warning (4.01 sec)mysql> show warnings;
+———+——+———————————————————————————————————————————————-+
| Level | Code | Message |
+———+——+———————————————————————————————————————————————-+
| Warning | 1810 | InnoDB: IO Read error: (2, No such file or directory) Error opening ‘./test/sbtest1.cfg’, will attempt to import without schema verification |
+———+——+———————————————————————————————————————————————-+
1 row in set (0.00 sec)
提示没有sbtest1.cfg,但是数据已经完整的插入进去了。实际上tablespace 的Import功能很早就有了,但MySQL5.5及之前的版本存在几点限制:
1.如果目标实例上表在tablespace中存储的id和ibd中的id不匹配,则会导入失败,通常的做法是通过不断创建/rename表的方式推高table id,或者直接改ibd文件头的方式来让其匹配;
2.如果没有主键的话,还要考虑到全局row id的问题;
3.迁移表的ibd上的最大LSN不能超过目标机器的LSN;
4. change buffer导致的DML缓存,脏页等问题。
另外cfg文件也不是必须的,因为他只是从ibdata中抽取的表的定义,但如果表空间也损坏了呢?这种情况下可能就没有作用了。
很显然早就有人意识到这个问题,看看这个bug report http://bugs.mysql.com/bug.php?id=66715, 如果知道表的schema的话,我们就可以在另外一个实例上生成一个cfg文件,因为cfg并不要求和ibd文件是完全对应的。
不过,一个明显的问题是,如果表空间损坏,实例起不来,做不了crash recovery,那么change buffer的操作可能也没有同步到ibd中,这种情况,是不是意味着数据丢失呢?
PS:执行flush tables tbname with read lock也同样会生成cfg文件
简单的跟了下代码
a. FLUSH TABLES sbtest1 FOR EXPORT
server层的入口函数为flush_tables_for_export
Innodb层进行处理的backtrace为:
flush_tables_for_export->open_and_lock_tables->lock_tables->mysql_lock_tables->lock_external->handler::ha_external_lock->ha_innobase::external_lock
switch (prebuilt->table->quiesce) { case QUIESCE_START: /* Check for FLUSH TABLE t WITH READ LOCK; */ if (!srv_read_only_mode && thd_sql_command(thd) == SQLCOM_FLUSH && lock_type == F_RDLCK) { row_quiesce_table_start(prebuilt->table, trx); /* Use the transaction instance to track UNLOCK TABLES. It can be done via START TRANSACTION; too implicitly. */ ++trx->flush_tables; } break; case QUIESCE_COMPLETE: /* Check for UNLOCK TABLES; implicit or explicit or trx interruption. */ if (trx->flush_tables > 0 && (lock_type == F_UNLCK || trx_is_interrupted(trx))) { row_quiesce_table_complete(prebuilt->table, trx); ut_a(trx->flush_tables > 0); --trx->flush_tables; } break;
其中,row_quiesce_table_start是执行flush table for export的逻辑,row_quiesce_table_complete是unlock tables调用的逻辑
代码在 @file row/row0quiesce.cc 中
a1) row_quiesce_table_start
主要做以下几件事情
*停止purge操作
if (trx_purge_state() != PURGE_STATE_DISABLED) { trx_purge_stop(); }
因此在完成copy后,需要及时的Unlock tables,防止purge线程停止太久,导致history list过长
*合并change buffer :ibuf_contract_in_background(table->id, TRUE)
*确保所有的脏页都刷到磁盘
buf_LRU_flush_or_remove_pages(
table->space, BUF_REMOVE_FLUSH_WRITE, trx);
*写cfg文件
row_quiesce_write_cfg(table, trx->mysql_thd
row_quiesce_write_header:cfg文件头包括cfg文件版本号IB_EXPORT_CFG_VERSION_V1,当前服务器的hostname,表名,下一个自增列的值,page size, table flag,以及列的个数
row_quiesce_write_table:写入表的列的属性信息,包括长度,类型,位置等
row_quiesce_write_indexes:写入每个索引的元数据信息
a2)row_quiesce_table_complete(执行Unlock tables)
*删除cfg文件(os_file_delete_if_exists)
*继续purge操作(trx_purge_run())
b.import数据
backtrace为:
ha_innobase::discard_or_import_tablespace->row_import_for_mysql
大概看了下,主要包括以下几个步骤:
*读取cfg文件,读取的数据保存到结构体row_import中
err = row_import_read_cfg(table, trx->mysql_thd, cfg);
*检查表的定义和cfg中的是否匹配
*扫描ibd文件中的所有page,检查其是否corruption
*检查ibuf bitmap,如果有标记为IBUF_BITMAP_BUFFERED的,表示存在change buffer的操作没有merge进来,可能会丢失数据。
仅仅写日志提示,可以继续正常import数据
*调整聚集索引的segment header,例如其中记录的space id
err = btr_root_adjust_on_import(index);
*purge 聚集索引上的标记删除记录
*调整二级索引根节点和叶子节点segment header,更新space id, purgw标记删除的无效记录
*确保当前实例下一个可用的DB_ROW_ID 要大于当前import的表上最大的DB_ROW_ID
*将产生的脏页刷到磁盘,并从buffer pool中移除对应的page
buf_LRU_flush_or_remove_pages(
prebuilt->table->space, BUF_REMOVE_FLUSH_WRITE, trx);
*更新系统表SYS_INDEXES中记录的索引信息
err = row_import_update_index_root(trx, table, false, true);
*移除表的discade标记
//////////////////////
参考:
http://mysqlblog.fivefarmers.com/2012/09/07/understanding-innodb-transportable-tablespaces-in-mysql-5-6/
http://planet.mysql.com/entry/?id=34283