---
title: 'Dumpling 导出表内并发优化丨 TiDB 工具'
date: '2022-01-04T00:00:00+08:00'
status: publish
permalink: /blog/dumpling-export-in-table-concurrency-optimisation
author:
    - 李淳竹
    - 李淳竹
excerpt: '之前的文章对 Dumpling 进阶使用进行了介绍，本文接下来将会介绍 Dumpling 内部表内并发的优化逻辑，从而帮助大家更深刻地理解 Dumpling 工作原理。'
type: post
id: 3082
category:
    - 产品技术解读
tag:
    - Dumpling
post_format: []
profile:
    - 李淳竹
algolia_posts_post_records_count:
    - '4'
---
> 李淳竹（lichunzhu），TiDB 研发工程师  
> SIG 组：Migrate SIG Community，主要涵盖 TiDB 数据处理工具，包含 TiDB 数据备份/导入导出，TiDB 数据变更捕获，其他数据库数据迁移至 TiDB 等

前言
--

[Dumpling](https://github.com/pingcap/dumpling) 是由 Go 语言编写的用于对数据库进行数据导出的工具。目前支持 MySQL 协议的数据库，并且针对 TiDB 的特性进行了优化。[Go Dumpling! 让导出数据更稳定](https://cn.pingcap.com/blog/go-dumpling-makes-exporteddata-stable)文章对 Dumpling 进阶使用进行了介绍。**本文接下来将会介绍 Dumpling 内部表内并发的优化逻辑，从而帮助大家更深刻地理解 Dumpling 工作原理。**

为什么需要表内并发
---------

Dumpling 内部的导出逻辑可以用生产消费者模型进行诠释。生产者线程会遍历待导出数据库表集合，再会将生成好的导出 SQL 发送给消费者线程，由消费者线程将 SQL 执行结果格式化后写入文件。不难看出，不同消费者间可以互不干扰地进行并发导出。

由上文较容易推导的是，待导出的数据表彼此并无联系，可以由不同消费者并发导出。但大部分业务场景中，表和表之间的数据量差异巨大，很容易会出现线程空转在一张大表的情况。因此需要将大表划分为更小的“导出单元”(后文将简称为 chunk )以便于消费者线程并行导出，从而提升导出速度。chunk 划分也应该保证尽可能均匀，不均匀的 chunk 划分与大表小表并发导出的问题类似，**会使得导出时间加倍，并极大提升数据库服务器内存使用**。

导出 MySQL 时的表内并发
---------------

那么如何将大表划分为更小且较为均匀的 chunk 呢？可以想到，相比于其他类型，整型数字可以较为均匀地划分为多个 limit 范围，是个最为理想的划分方式。同时，为了保证划分的整数范围能够命中索引，避免重复扫全表从而浪费计算资源，使用的划分范围应该为索引的第一列。由此可以得到针对 MySQL 的表内并发划分方式：

首先选取第一列为整数的索引列记为 field，按照主键、唯一索引、具有最大 [Cardinality](https://dev.mysql.com/doc/refman/8.0/en/show-index.html) 的索引的顺序进行选取，从而保证该列整型数据尽量不同。选择好整数列后，Dumpling 通过 explain 语句粗略估算该表在限定条件下会导出的数据行数并记为 count。根据开头指定了划分行数大小的参数 rows，可以得到 Dumpling 需要将数据划分为 count/rows 个 chunk。随后通过 select min(field), max(field) 的方式得出在限定条件下的数据中的最大最小 field 记为 max\_field 与 min\_field。假设在这个范围内数据是呈现大体均匀分布的，则可以求出划分步长为 d=(max\_field-min\_field)\*rows/count。各个表内并发 chunk 通过 where 条件约束，范围分别为 \[min\_field, min\_field+d), \[min\_field+d, min\_field+2d) …

从上述实现可以看出指定 rows 后划分 chunk 并不一定为 rows 行。同时，调大 rows 将直接增大各个 chunk 的步长范围即增大各个 chunk 的数据量。因此，如果发现 Dumpling 导出时对数据库内存消耗过大时，可以适当调小 rows 从而减小各个 chunk 的数据量。在实际导出场景中，rows 设置应较为适中：过大会消耗过多内存，且容易使并发效果不好；过小则容易导致 Dumpling 频繁向数据库请求少量数据，使导出速度下降。在目前的实践场景中，配置 --rows=200000 一般能够兼顾并发效果与导出速度。

导出 TiDB v3.0/v4.0 时的表内并发
------------------------

从上文可以看出，当用户表不存在分布均匀的整数索引，或者 explain 语句获取数据行数的结果不准确时，表内并发效果将大打折扣。那么，TiDB 和 Dumpling 会怎么处理这一问题呢？在 [TiDB 数据库如何计算](https://docs.pingcap.com/zh/tidb/stable/tidb-computing/#%E8%A1%A8%E6%95%B0%E6%8D%AE%E4%B8%8E-key-value-%E7%9A%84%E6%98%A0%E5%B0%84%E5%85%B3%E7%B3%BB-1)一文中，提到了 TiDB 会为表中每行数据分配一个行 ID，用 RowID 表示。该 RowID 表内唯一且可以通过 select \_tidb\_rowid 的方式直接从数据库中获取。因此，简单的思路是直接将 \_tidb\_rowid 当作上文中的整型主键，采用相同的方式进行 chunk 划分即可。

然而，在 [TiDB 高并发写入场景最佳实践](https://docs.pingcap.com/zh/tidb/stable/high-concurrency-best-practices/)中提到，为了避免 TiDB 写入热点，TiDB 表时常会使用 AUTO\_RANDOM 列或在建表时加入 SHARD\_ROW\_ID\_BITS 参数。这些参数会使得 \_tidb\_rowid 列分布极其不均匀，从而导致 Dumpling 导出表内并发划分 chunk 时划分不准确形成大 chunk，影响导出速度甚至引发 OOM。

在 [TiDB 数据库的存储](https://docs.pingcap.com/zh/tidb/stable/tidb-storage/#region)中，可以得到 TiDB 的数据映射为 KV 键值对后，以 range region 的形式存储在 TiKV 上，每个 region 保存了 \[StartKey，EndKey) 范围的数据且 TiKV 会尽量保持每个 Region 中保存的数据不超过一定的大小。这些特性非常有利于 Dumpling 划分均匀的 chunk 数据。因此，Dumpling 通过 TiDB 的 INFORMATION\_SCHEMA 库下的 TIKV\_REGION\_STATUS 表获取导出目标表所有 Region 的 StartKey，解码出所需要的 row\_id，再使用得到 rowid 作为 WHERE 条件划分出 chunk。

从上述实现中可以看出 Dumpling 的表内并发的划分尺度为 region 大小，rows 的具体值已经不对划分结果产生影响。但是 rows 值设置与否仍将决定 Dumpling 是否采取表内并发的方式导出 TiDB 数据库。

导出 TiDB v5.0 时的表内并发
-------------------

TiDB v5.0.0 开始支持了[聚簇索引](https://docs.pingcap.com/zh/tidb/stable/clustered-indexes/)来避免 TiDB 此前使用 rowid 时的回表操作，提升写入查询速度。开启聚簇索引的表将不再有 \_tidb\_rowid 列。同时，在 split region 等特定场景下，region 的 StartKey 也不一定为合法值。但上文按 region 划分的思路仍然是行之有效的方法，然而需要更好的获取 region 边界划分数据的方法。

为了解决这一问题，TiDB 在 v5.0.0 及以上版本支持了 SELECT fields FROM table TABLESAMPLE REGIONS() 语法。执行该 SQL 后，TiKV 会扫描出表涉及到的每个 region 并获取第一个合法 kv 对，再将得到的数据返回给 Dumpling。例如使用该 SQL SELECT 聚簇索引的各个列时，该 SQL 会返回该表每个 REGION 中第一行聚簇索引的各列值用于均匀划分 chunk。

Dumpling 后续开发计划
---------------

以下为 Dumpling 后续开发的一些计划与设想。目前 Dumpling 已经迁移到 tidb repo，欢迎大家在 [Dumpling Repo](https://github.com/pingcap/tidb) 一起交流讨论，参与开发。

- **[支持导出更多种类的源数据库](https://github.com/pingcap/dumpling/issues/11)(issue#11)**

一般来说，只要需要支持的数据库有对应的 database driver 或 client，比如 Oracle 数据库的 golang driver [godror](https://github.com/godror/godror)，都可以轻微改造导出语句和调用的 Go 代码库后就实现该数据库的导出支持。这里也欢迎社区的小伙伴们参与，帮助 Dumpling 支持导出更多类型的数据库。

- **[支持导出 Sequence](https://github.com/pingcap/dumpling/issues/61)(issue#61)**

Dumpling 目前不支持导出 TiDB Sequence，支持该功能将使导出功能更完整。

- **[支持 checksum 校验](https://github.com/pingcap/tidb/issues/30319)**

Dumpling 需要支持 checksum\[4\] \[5\] 校验来保证导出数据的正确性。

- **[支持 checkpoint(issue#10)](https://github.com/pingcap/dumpling/issues/10)**

支持 Dumpling 使用 snapshot 模式导出 TiDB 时部分导出后从断点继续导出。

> 联 系：channel #sig-migrate in the tidbcommunity slack workspace, you can join this channel through [this invitation link](https://slack.tidb.io/invite?team=tidb-community&channel=sig-migrate&ref=pingcap-community)。