在最新版本8.0.22上还支持了启动和停止某个Secondary Engine。

MySQL官方集成了RAPID来为MySQL提供实时的数据分析服务,同时支持InnoDB和RAPID,但未开源,开源MySQL引入Secondary Engine,有助于我们集成其他存储引擎或者数据库。

本文是基于最新版本的MySQL-8.0.22源码解读的。

  • 安装一个secondary engine MOCK 使用Secondary Engine之前需要安装插件,目前源码中有模拟Secondary Engine的插件ha_mock.so

  • 与Secondary Engine有关的系统变量

    1. SET @use_secondary_engine= "OFF";
    2. SET @use_secondary_engine= "FORCED";
  • 表定义时需要指明使用Secondary Engine,如:

  • 加载和卸载数据的语法如下:

    1. ALTER TABLE T1 SECONDARY_LOAD;
    2. ALTER TABLE T1 SECONDARY_UNLOAD;

    如图所示,Secondary Engine实际上是MySQL sever上同时支持两个存储引擎,把一部分主引擎上的数据,在Secondary Engine上也保存一份,然后查询的时候会根据优化器的的选择决定在哪个引擎上处理数据。

    加载/卸载数据

TABLE::read_set

TABLE::read_set 用来标记哪些coloum的数据需要load到Secondary Engine上。默认这个表的所有columns都需要load到Secondary Engine上,除非这个column上标记了NOT SECONDARY的属性。

  1. bool Sql_cmd_secondary_load_unload::mysql_secondary_load_or_unload(
  2. THD *thd, TABLE_LIST *table_list) {
  3. ...
  4. // read_set. It is the responsibility of the secondary engine handler to load
  5. // only the columns included in the read_set.
  6. bitmap_clear_all(table_list->table->read_set);
  7. for (Field **field = table_list->table->field; *field != nullptr; ++field) {
  8. // Skip hidden generated columns.
  9. if (bitmap_is_set(&table_list->table->fields_for_functional_indexes,
  10. (*field)->field_index))
  11. continue;
  12. // Skip columns marked as NOT SECONDARY.
  13. if ((*field)->flags & NOT_SECONDARY_FLAG) continue;
  14. // Mark column as eligible for loading.
  15. table_list->table->mark_column_used(*field, MARK_COLUMNS_READ);
  16. // bitmap_set_bit(read_set, field->field_index);
  17. }
  18. ...
  19. // Initiate loading into or unloading from secondary engine.
  20. const bool error =
  21. is_load
  22. ? secondary_engine_load_table(thd, *table_list->table)
  23. : secondary_engine_unload_table(
  24. ...

两阶段加载

加载数据到Secondary Engine的过程有两阶段组成:

  • 第一阶段, ha_prepare_load_table
    prepare阶段通常是很短的一段时间,需要持有一把MDL_EXCLUSIVE 表锁,持有锁的时间很短,主要是为了保证在开始数据加载之前提交对于该表的所有DML操作。
  • 第二阶段, ha_load_table 这个阶段是真正加载数据的阶段,使用InnoDB的Parallel_reader_adapter实现并行扫描数据以提高加载效率。
  1. // InnoDB parallel scan context
  2. struct handler {
  3. // Parallel scan interface, could be explored to speed up offload process
  4. int parallel_scan_init(void *&scan_ctx, size_t &num_threads);
  5. int parallel_scan(void *scan_ctx, void **thread_ctxs,
  6. Load_init_cbk init_fn, Load_cbk load_fn, Load_end_cbk end_fn);
  7. void parallel_scan_end(void *scan_ctx);
  8. // Allows concurrent DML in the offload process
  9. int ha_prepare_load_table(const TABLE &table);
  10. int ha_load_table(const TABLE &table);
  11. int ha_unload_table(const char *db_name, const char *table_name,
  12. bool error_if_not_loaded);
  13. void ha_set_primary_handler(handler *primary_handler);
  14. };

2020-11-wenjing2.png

在query执行之前,优化器的最后阶段增加了optimize_secondary_engine,但并不是所有的query都要经过optimize_secondary_engine。目的主要是避免在Primary engine上执行很快的query经过secondary engine执行。

  • 首先先走正常的优化流程 unit->optimize()
  • 如果current_query_cost大于secondary_engine_cost_threshold If (current_query_cost < variables.secondary_engine_cost_threshold) return false; optimize_secondary_engine

Mock

Mock是为了对MySQL进行与Secondary Engine相关的功能测试而写的一个Secondary Engine的demo。他定义了用于适配Secondary Engine的接口。 mock 的源码在 router/src/mock_server目录下, 与secondary engine适配的接口在 storage/secondary_engine_mock/ha_mock.h storage/secondary_engine_mock/ha_mock.cc

Secondary Engine是MySQL为了支持多引擎提供一种方法和实现框架。在此基础上,MySQL可以根据不同存储引擎对数据处理的特点来把不同的查询计划匹配到合适的存储引擎的上执行,从而发挥多种存储引擎各自的优点,优化整个SQL的查询效率。为多模架构和异构数据库的实现提供了一种框架。 比如,我们可以利用Secondary Engine接入ClickHouse,来承接分析型的查询。

Secondary Engine的基础框架已经搭建起来了,实现了一个用于功能测试demo mock。但是还不够完善: 目前load数据只支持存量数据的加载,还不能支持增量数据的加载。不能支持实时的数据同步到secondary engine上。 目前还没有直接可以作为MySQL的Secondary Engine的存储引擎,如果接入,需要做一些适配的研发工作。

前景