使用 TiDB 读取 TiFlash

本文档介绍如何使用 TiDB 读取 TiFlash 副本。

TiDB 提供三种读取 TiFlash 副本的方式。如果添加了 TiFlash 副本,而没有做任何 engine 的配置,则默认使用 CBO 方式。

智能选择

对于创建了 TiFlash 副本的表,TiDB 优化器会自动根据代价估算选择是否使用 TiFlash 副本。具体有没有选择 TiFlash 副本,可以通过 descexplain analyze 语句查看,例如:

  1. desc select count(*) from test.t;
  1. +--------------------------+---------+--------------+---------------+--------------------------------+
  2. | id | estRows | task | access object | operator info |
  3. +--------------------------+---------+--------------+---------------+--------------------------------+
  4. | StreamAgg_9 | 1.00 | root | | funcs:count(1)->Column#4 |
  5. | └─TableReader_17 | 1.00 | root | | data:TableFullScan_16 |
  6. | └─TableFullScan_16 | 1.00 | cop[tiflash] | table:t | keep order:false, stats:pseudo |
  7. +--------------------------+---------+--------------+---------------+--------------------------------+
  8. 3 rows in set (0.00 sec)
  1. explain analyze select count(*) from test.t;
  1. +--------------------------+---------+---------+--------------+---------------+----------------------------------------------------------------------+--------------------------------+-----------+------+
  2. | id | estRows | actRows | task | access object | execution info | operator info | memory | disk |
  3. +--------------------------+---------+---------+--------------+---------------+----------------------------------------------------------------------+--------------------------------+-----------+------+
  4. | StreamAgg_9 | 1.00 | 1 | root | | time:83.8372ms, loops:2 | funcs:count(1)->Column#4 | 372 Bytes | N/A |
  5. | └─TableReader_17 | 1.00 | 1 | root | | time:83.7776ms, loops:2, rpc num: 1, rpc time:83.5701ms, proc keys:0 | data:TableFullScan_16 | 152 Bytes | N/A |
  6. | └─TableFullScan_16 | 1.00 | 1 | cop[tiflash] | table:t | tiflash_task:{time:43ms, loops:1, threads:1}, tiflash_scan:{...} | keep order:false, stats:pseudo | N/A | N/A |
  7. +--------------------------+---------+---------+--------------+---------------+----------------------------------------------------------------------+--------------------------------+-----------+------+

cop[tiflash] 表示该任务会发送至 TiFlash 进行处理。如果没有选择 TiFlash 副本,可尝试通过 analyze table 语句更新统计信息后,再查看 explain analyze 结果。

需要注意的是,如果表仅有单个 TiFlash 副本且相关节点无法服务,智能选择模式下的查询会不断重试,需要指定 Engine 或者手工 Hint 来读取 TiKV 副本。

Engine 隔离

Engine 隔离是通过配置变量来指定所有的查询均使用指定 engine 的副本,可选 engine 为 “tikv”、”tidb” 和 “tiflash”(其中 “tidb” 表示 TiDB 内部的内存表区,主要用于存储一些 TiDB 系统表,用户不能主动使用),分别有 2 个配置级别:

  1. TiDB 实例级别,即 INSTANCE 级别。在 TiDB 的配置文件添加如下配置项:

    1. [isolation-read]
    2. engines = ["tikv", "tidb", "tiflash"]

    实例级别的默认配置为 ["tikv", "tidb", "tiflash"]

  2. 会话级别,即 SESSION 级别。设置语句:

    1. set @@session.tidb_isolation_read_engines = "逗号分隔的 engine list";

    或者

    1. set SESSION tidb_isolation_read_engines = "逗号分隔的 engine list";

    会话级别的默认配置继承自 TiDB 实例级别的配置。

最终的 engine 配置为会话级别配置,即会话级别配置会覆盖实例级别配置。比如实例级别配置了 “tikv”,而会话级别配置了 “tiflash”,则会读取 TiFlash 副本。当 engine 配置为 “tikv, tiflash”,即可以同时读取 TiKV 和 TiFlash 副本,优化器会自动选择。

使用 TiDB 读取 TiFlash - 图1

注意

由于 TiDB Dashboard 等组件需要读取一些存储于 TiDB 内存表区的系统表,因此建议实例级别 engine 配置中始终加入 “tidb” engine。

如果查询中的表没有对应 engine 的副本,比如配置了 engine 为 “tiflash” 而该表没有 TiFlash 副本,则查询会报该表不存在该 engine 副本的错。

手工 Hint

手工 Hint 可以在满足 engine 隔离的前提下,强制 TiDB 对于某张或某几张表使用指定的副本,使用方法为:

  1. select /*+ read_from_storage(tiflash[table_name]) */ ... from table_name;

如果在查询语句中对表设置了别名,在 Hint 语句中必须使用别名才能使 Hint 生效。比如:

  1. select /*+ read_from_storage(tiflash[alias_a,alias_b]) */ ... from table_name_1 as alias_a, table_name_2 as alias_b where alias_a.column_1 = alias_b.column_2;

其中 tiflash[] 是提示优化器读取 TiFlash 副本,亦可以根据需要使用 tikv[] 来提示优化器读取 TiKV 副本。更多关于该 Hint 语句的语法可以参考 READ_FROM_STORAGE

如果 Hint 指定的表在指定的引擎上不存在副本,则 Hint 会被忽略,并产生 warning。另外 Hint 必须在满足 engine 隔离的前提下才会生效,如果 Hint 中指定的引擎不在 engine 隔离列表中,Hint 同样会被忽略,并产生 warning。

使用 TiDB 读取 TiFlash - 图2

注意

MySQL 命令行客户端在 5.7.7 版本之前默认清除了 Optimizer Hints。如果需要在这些早期版本的客户端中使用 Hint 语法,需要在启动客户端时加上 --comments 选项,例如 mysql -h 127.0.0.1 -P 4000 -uroot --comments

三种方式之间关系的总结

上述三种读取 TiFlash 副本的方式中,Engine 隔离规定了总的可使用副本 engine 的范围,手工 Hint 可以在该范围内进一步实现语句级别及表级别的细粒度的 engine 指定,最终由 CBO 在指定的 engine 范围内根据代价估算最终选取某个 engine 上的副本。

使用 TiDB 读取 TiFlash - 图3

注意

  • TiDB 4.0.3 版本之前,在非只读 SQL 语句中(比如 INSERT INTO ... SELECTSELECT ... FOR UPDATEUPDATE ...DELETE ...)读取 TiFlash,行为是未定义的。
  • TiDB 4.0.3 到 6.2.0 之间的版本,TiDB 内部会对非只读 SQL 语句忽略 TiFlash 副本以保证数据写入、更新、删除的正确性。对应的,如果使用了智能选择的方式,TiDB 会自动选择非 TiFlash 副本;如果使用了 Engine 隔离的方式指定读取 TiFlash 副本,则查询会报错;而如果使用了手工 Hint 的方式,则 Hint 会被忽略。
  • TiDB 6.3.0 到 7.0.0 之间的版本,如果 TiFlash 副本被允许使用,你可以通过变量 tidb_enable_tiflash_read_for_write_stmt 控制是否允许 TiDB 在处理非只读 SQL 语句时使用 TiFlash 副本。
  • 从 TiDB 7.1.0 起,如果 TiFlash 副本被允许使用且当前会话的 SQL 模式为非严格模式(即 sql_mode 值不包含 STRICT_TRANS_TABLESSTRICT_ALL_TABLES),TiDB 在处理非只读 SQL 语句时会根据代价估算自动选择是否使用 TiFlash 副本。