上面情况出现的主要原因就是MySQL不支持原子的DDL。从图1可以看出,MySQL8.0以前,metadata在Server layer是存储在MyISAM引擎的系统表里,对于事务型引擎Innodb则自己存储一份metadata。这也导致MySQL存在如下一些弊端:

    1. 两份系统表存储的信息有所不同,访问Server layer以及存储引擎需要使用不同API,这种设计导致了不能很好的统一对系统metadata的访问。另外两份API,也同时增加了代码的维护量。
    2. 由于Server layer的metadata存储在非事务引擎(MyISAM)里,所以在进行crash recovery的时候就不能维持原子性。
    3. DDL的非原子性使得Replication处理异常情况变得更加复杂。比如DROP TABLE t1, t2; 如果DROP t1成功,但是DROP t2失败,Replication就无法保证主备一致性了。

    图1: MySQL Data Dictionary before MySQL8.0

    atomic-ddl-2.png

    图2: MySQL Data Dictionary in MySQL8.0

    图2我们可以看到,Server layer(后面简称SL)以及Storage Engine(后面简称SE) 使用同一份data dictionary(后面简称DD)用来存储metadata。SL和SE将各自需要的metadata存入DD中。由于DD使用Innodb作为存储引擎,所以crash recovery的时候,DD可以安全的进行事务回滚。

    接下来我们以CREATE TABLE为例从源码上简单看一下MYSQL8.0是如何实现原子DDL的。

    CREATE TABLE实现的流程图如下:

    1. 该函数将会为Innodb存储引擎创建它自己需要的系统列。实际上就是把原来Innodb自己的系统表统一到DD中。
    2. */
    3. int
    4. ha_innobase::get_extra_columns_and_keys(
    5. const HA_CREATE_INFO*,
    6. const List<Create_field>*,
    7. const KEY*,
    8. uint,
    9. dd::Table* dd_table)
    10. {
    11. DBUG_ENTER("ha_innobase::get_extra_columns_and_keys");
    12. THD* thd = ha_thd();
    13. dd::Index* primary = nullptr;
    14. bool has_fulltext = false;
    15. const dd::Index* fts_doc_id_index = nullptr;
    16. /* 检查各个定义的索引是否合法。*/
    17. for (dd::Index* i : *dd_table->indexes()) {
    18. /* The name "PRIMARY" is reserved for the PRIMARY KEY */
    19. ut_ad((i->type() == dd::Index::IT_PRIMARY)
    20. == !my_strcasecmp(system_charset_info, i->name().c_str(),
    21. primary_key_name));
    22. if (!my_strcasecmp(system_charset_info,
    23. i->name().c_str(), FTS_DOC_ID_INDEX_NAME)) {
    24. ut_ad(!fts_doc_id_index);
    25. ut_ad(i->type() != dd::Index::IT_PRIMARY);
    26. fts_doc_id_index = i;
    27. }
    28. /* 验证索引算法是否有效 */
    29. switch (i->algorithm()) {
    30. ...
    31. }
    32. /* 验证并处理全文索引 */
    33. if (has_fulltext) {
    34. ...
    35. }
    36. /* 如果当前没有定义主键,Innodb将自动增加DB_ROW_ID作为主键。 */
    37. if (primary == nullptr) {
    38. dd::Column* db_row_id = dd_add_hidden_column(
    39. dd_table, "DB_ROW_ID", DATA_ROW_ID_LEN,
    40. dd::enum_column_types::INT24);
    41. if (db_row_id == nullptr) {
    42. DBUG_RETURN(ER_WRONG_COLUMN_NAME);
    43. }
    44. primary = dd_set_hidden_unique_index(
    45. dd_table->add_first_index(),
    46. primary_key_name,
    47. db_row_id);
    48. }
    49. /* 为二级索引增加主键列 */
    50. ut_allocator<const dd::Index_element*>> pk_elements;
    51. if (index == primary) {
    52. continue;
    53. }
    54. pk_elements.clear();
    55. for (const dd::Index_element* e : primary->elements()) {
    56. if (e->is_prefix() ||
    57. std::search_n(index->elements().begin(),
    58. index->elements().end(), 1, e,
    59. [](const dd::Index_element* ie,
    60. const dd::Index_element* e) {
    61. return(&ie->column()
    62. == &e->column());
    63. }) == index->elements().end()) {
    64. pk_elements.push_back(e);
    65. }
    66. }
    67. for (const dd::Index_element* e : pk_elements) {
    68. auto ie = index->add_element(
    69. const_cast<dd::Column*>(&e->column()));
    70. ie->set_hidden(true);
    71. ie->set_order(e->order());
    72. }
    73. }
    74. /* 增加系统列 DB_TRX_ID, DB_ROLL_PTR. */
    75. dd::Column* db_trx_id = dd_add_hidden_column(
    76. dd_table, "DB_TRX_ID", DATA_TRX_ID_LEN,
    77. dd::enum_column_types::INT24);
    78. if (db_trx_id == nullptr) {
    79. DBUG_RETURN(ER_WRONG_COLUMN_NAME);
    80. }
    81. dd::Column* db_roll_ptr = dd_add_hidden_column(
    82. dd_table, "DB_ROLL_PTR", DATA_ROLL_PTR_LEN,
    83. dd::enum_column_types::LONGLONG);
    84. if (db_roll_ptr == nullptr) {
    85. DBUG_RETURN(ER_WRONG_COLUMN_NAME);
    86. }
    87. dd_add_hidden_element(primary, db_trx_id);
    88. dd_add_hidden_element(primary, db_roll_ptr);
    89. /* Add all non-virtual columns to the clustered index,
    90. unless they already part of the PRIMARY KEY. */
    91. for (const dd::Column* c : const_cast<const dd::Table*>(dd_table)->columns()) {
    92. if (c->is_hidden() || c->is_virtual()) {
    93. continue;
    94. }
    95. if (std::search_n(primary->elements().begin(),
    96. primary->elements().end(), 1,
    97. c, [](const dd::Index_element* e,
    98. const dd::Column* c)
    99. {
    100. return(!e->is_prefix()
    101. && &e->column() == c);
    102. == primary->elements().end()) {
    103. }
    104. }
    105. DBUG_RETURN(0);
    106. }
    107. template <typename T>
    108. Dictionary_client::store(T* object)
    109. {
    110. ...
    111. /* 调用下面函数完成存储 */
    112. if (Storage_adapter::store(m_thd, object))
    113. return true;
    114. ...
    115. }
    116. /* 该函数负责将DD对象写入对应的系统表中。 */
    117. template <typename T>
    118. bool Storage_adapter::store(THD *thd, T *object)
    119. {
    120. // 如果是测试或者未到真正需要建表的阶段,只存入缓存,不进行持久化存储。
    121. if (s_use_fake_storage ||
    122. bootstrap::DD_bootstrap_ctx::instance().get_stage() <
    123. bootstrap::Stage::CREATED_TABLES)
    124. {
    125. instance()->core_store(thd, object);
    126. return false;
    127. }
    128. // 这里会验证DD对象的有效性
    129. if (object->impl()->validate())
    130. {
    131. DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
    132. return true;
    133. }
    134. // 切换上下文,包括更新系统表的时候关闭binlog、修改auto_increament_increament增量、设置一些相关变量等与修改DD相关的上下文。
    135. Update_dictionary_tables_ctx ctx(thd);
    136. ctx.otx.register_tables<T>();
    137. DEBUG_SYNC(thd, "before_storing_dd_object");
    138. // object->impl()->store 这里会将DD对象存入相关的系统表。具体比如表,列, 表空间是如何持久化到系统表中的,由于篇幅有限,我们将在以后的月报中继续剖析。
    139. if (ctx.otx.open_tables() || object->impl()->store(&ctx.otx))
    140. {
    141. DBUG_ASSERT(thd->is_system_thread() || thd->killed || thd->is_error());
    142. return true;
    143. }
    144. // Do not create SDIs for tablespaces and tables while creating
    145. // dictionary entry during upgrade.
    146. if (bootstrap::DD_bootstrap_ctx::instance().get_stage() >
    147. bootstrap::Stage::CREATED_TABLES &&
    148. dd::upgrade_57::allow_sdi_creation() &&
    149. sdi::store(thd, object))
    150. return true;
    151. return false;

    综上篇章简要的描述了MySQL8.0实现原子DDL的背景以及一些重点的数据结构,并对CREATE TABLE过程,以及创建过程中用到的几个重要函数进行了分析。但是原子DDL的实现是一个非常大的工程,本篇月报由于篇幅问题,只是挖了冰山一角。以后的月报会继续对原子DDL的实现进行分析,希望大家持续关注。