最近半年一直在做TiDB的迁移项目,从原来的MySQL分库分表方案迁移到TiDB分布式数据库,踩了不少坑也积累了一些经验。

TiDB这两年很火,作为一个兼容MySQL协议的分布式数据库,支持HTAP,既能做OLTP也能做OLAP,而且不用分库分表能无限水平扩展,听起来很美好。但是真正迁移起来才发现,不是简单地把数据导过去就行了,有很多坑要踩,也有很多细节要注意。

今天这篇文章就来聊聊我们的TiDB迁移实战,从旧系统到新系统是怎么做的,踩了哪些坑,有哪些经验,希望能给正在做或者准备做TiDB迁移的朋友一些参考。

一、为什么要迁移到TiDB

先说说我们为什么要迁移到TiDB。

我们最开始用的是MySQL单库单表,后来数据量越来越大,单表到了几千万甚至上亿,查询越来越慢,加索引也要锁表很久影响业务。所以就做了分库分表,用的是ShardingSphere(当时还叫Sharding-JDBC),分了4个库每个库8张表一共32张表。

分库分表之后性能确实好了很多,单表的数据量降下来了查询也快了。但是分库分表也带来了很多问题:

  1. 跨库查询很麻烦:很多查询需要跨多个库多个表,ShardingSphere要把所有的表都查一遍然后合并结果,性能很差。而且很多复杂的SQL不支持,比如子查询、联合查询、聚合函数等,要么不支持要么性能很差。
  2. 分布式事务很麻烦:分库分表之后原来的单库事务变成了分布式事务。ShardingSphere支持XA和柔性事务,但XA性能差,柔性事务开发成本高,而且都有一些坑,用起来很麻烦。
  3. 扩容很麻烦:分库分表扩容很麻烦,原来分了4个库32张表,要扩容就要重新分片,数据要重新迁移,很麻烦,而且扩容期间业务也要受影响。
  4. OLAP查询性能差:我们有一些统计分析的OLAP查询,需要扫描大量的数据。分库分表之后这些查询性能很差,因为要跨多个库多个表查询然后合并,而且MySQL本身也不适合做复杂的OLAP查询。
  5. 维护成本高:分库分表之后数据库变多了,维护成本也高了,备份、恢复、监控、优化都比单库麻烦很多。

这些问题越来越严重,我们就开始考虑新的方案,看了几个选择:

  • 继续用MySQL优化分库分表,但本质的问题解决不了,还是有跨库查询、分布式事务、扩容难等问题。
  • 用NewSQL数据库,比如TiDB、CockroachDB、OceanBase等。这些都是分布式数据库,兼容MySQL,不用分库分表,能水平扩展,支持事务,能解决我们的问题。
  • 用其他的分布式数据库,比如HBase、Cassandra等。但这些不兼容MySQL,迁移成本高,而且不支持复杂的SQL和事务,不适合我们的OLTP场景。

对比了几个NewSQL数据库,最终选了TiDB,主要是这几个原因:

  1. 兼容MySQL协议:TiDB高度兼容MySQL,大部分的MySQL语法和函数都支持,迁移成本比较低,应用代码不需要改太多。
  2. 不用分库分表:TiDB是分布式数据库,能自动分片水平扩展,不用手动分库分表,也不用在应用层做分片,大大简化了应用的开发。
  3. 支持HTAP:TiDB支持HTAP,既能做OLTP也能做OLAP。通过TiFlash列存引擎能支持复杂的分析查询,性能比MySQL好很多。这样我们就不用单独搞一套数据仓库做OLAP了。
  4. 社区活跃生态成熟:TiDB是PingCAP开源的,社区很活跃,文档也比较全,而且国内用的公司很多,有很多最佳实践和经验可以参考,遇到问题也能找到解决方案。
  5. 支持在线扩容和数据迁移:TiDB支持在线扩容和缩容,不用停机,而且有专门的数据迁移工具,能方便地从MySQL迁移数据到TiDB。

就这样我们决定迁移到TiDB,开始了半年的迁移项目。

二、迁移前的准备

迁移不是上来就导数据,而是要做很多准备工作。准备工作做好了迁移才能顺利。

我们迁移前的准备主要做了这几件事:

1. 调研和测试

最开始我们先调研了TiDB的原理、架构、特性,看了官方文档和很多最佳实践的文章,对TiDB有了一个整体的了解。

然后我们搭了一个测试环境,把生产的部分数据导进去,做了功能测试和性能测试,看看TiDB能不能支持我们的业务,性能怎么样,有哪些不兼容的地方。

测试发现了一些问题,比如有一些MySQL的语法和函数TiDB不支持或者行为不一样,还有一些复杂的SQL性能不如MySQL需要优化。这些问题我们都记录下来,在迁移之前或者迁移过程中逐个解决。

2. 容量规划

TiDB是分布式数据库,由多个组件组成,包括TiDB Server(SQL层)、TiKV(存储层)、PD(调度层),还有可选的TiFlash(列存)、监控组件等。所以要做好容量规划,看看需要多少台机器,每个组件分配多少资源。

我们根据我们的数据量和业务的QPS、延迟要求做了容量规划:

  • TiDB Server:负责SQL解析执行,无状态能水平扩展。我们根据QPS规划了几台,每台16核32G。
  • TiKV:负责数据存储和读写,有状态。我们根据数据量和性能要求规划了几台,每台16核64G,几块SSD盘。
  • PD:负责集群调度和元数据管理,一般3台就够了,因为PD是轻量级的不需要太多资源。
  • TiFlash:我们因为有OLAP的需求所以也部署了TiFlash,几台,每台配置比较高,因为列存需要较多的CPU和内存。
  • 监控组件:Prometheus、Grafana、Alertmanager等部署在单独的机器上。

而且我们还预留了一定的扩容空间,因为数据量还在增长,业务也在发展,不能刚好够现在用,要留有余地。

3. 兼容性评估

TiDB虽然高度兼容MySQL,但还是有一些不兼容的地方。所以要做兼容性评估,看看我们的SQL和应用有没有用到TiDB不支持的语法、函数或者特性。

我们用了TiDB提供的兼容性检查工具,也手动检查了我们的所有SQL和存储过程、触发器等,发现了一些不兼容的地方,比如:

  • 一些MySQL的函数TiDB不支持或者行为不一样。
  • 一些复杂的子查询、联合查询TiDB支持得不好或者性能差。
  • 存储过程、触发器、事件TiDB不支持。
  • 一些MySQL的特殊语法,比如SELECT ... FOR UPDATE,TiDB支持但行为和MySQL有点不一样。
  • 自增ID,TiDB支持但默认不是连续的,是趋势递增的,如果业务依赖连续的自增ID要注意。

这些不兼容的地方我们都记录下来,能改SQL的就改SQL,能改应用的就改应用,实在改不了的就找替代方案,确保迁移之后业务能正常运行。

4. 迁移工具的准备

TiDB提供了专门的数据迁移工具,比如Dumpling(导出数据)、TiDB Lightning(导入数据)、DM(数据迁移,支持全量+增量)等。

我们根据我们的迁移需求选择了DM工具,因为它支持全量迁移和增量同步,能做到在线迁移,业务不中断或者只中断很短的时间。

我们先在测试环境把这些工具都搭好,测试了数据的导出、导入和增量同步,看看速度怎么样有没有问题,确保迁移工具能正常工作。

5. 回滚方案

迁移是有风险的,所以一定要有回滚方案。万一迁移出问题了能快速地切回原来的MySQL,保证业务不受影响。

我们的回滚方案是迁移之后原来的MySQL不马上下线,而是继续运行作为备份。而且我们做了双向同步,TiDB的数据能同步回MySQL,这样万一TiDB出问题了能快速地切回MySQL,而且数据不会丢。

当然回滚方案要提前测试好,确保真的出问题的时候能顺利地回滚,不要等到出问题了才发现回滚方案有问题。

三、迁移的步骤

准备工作做好了就开始正式迁移了。我们的迁移分为这几个步骤:

第一步:搭建TiDB集群

首先是搭建TiDB集群。我们用了TiUP,TiDB的部署工具,很方便,能快速地部署一个TiDB集群,包括TiDB、TiKV、PD、TiFlash、监控组件等。

搭建的时候要注意这几点:

  • 机器的配置要符合要求,尤其是TiKV要用SSD盘,而且IOPS要够,不然性能会很差。
  • 网络要好,TiDB集群内部节点之间通信很多,所以网络要低延迟高带宽,最好在同一个机房同一个交换机,不要跨机房或者跨地域,不然性能会受很大影响。
  • 时间要同步,集群的所有节点时间要同步,用NTP,不然会有很多奇怪的问题。
  • 操作系统参数要优化,比如文件描述符、内存、swap、IO调度器等都要优化,不然性能会受影响。

搭建完之后要做集群的健康检查,看看所有的节点是不是都正常,监控是不是都能看到数据,确保集群是健康的。

第二步:全量数据迁移

集群搭好之后就开始全量数据迁移,也就是把原来MySQL里的所有数据导到TiDB里。

我们用的是DM工具,它能自动地从MySQL导出全量数据然后导入到TiDB里,而且速度很快,因为它支持多线程并发导入。

全量迁移要注意这几点:

  • 选择业务低峰期,全量迁移会对源MySQL有一定的压力,所以最好在业务低峰期做,避免影响业务。
  • 监控源库的性能,迁移过程中要监控源MySQL的CPU、IO、连接数等,确保不会因为迁移而影响业务。
  • 监控目标库的性能,也要监控TiDB的性能,包括TiDB、TiKV、PD的CPU、内存、IO、延迟等,确保导入能顺利进行。
  • 校验数据,全量迁移完成之后要校验数据,看看源库和目标库的数据是不是一致,包括表结构、数据量、数据内容等,确保数据没有丢失或者错误。

我们的全量迁移因为数据量比较大,有几个T,所以花了几天的时间。不过因为是在线的,对业务影响不大。

第三步:增量数据同步

全量迁移完成之后就开始增量数据同步,也就是把全量迁移之后源MySQL里新产生的数据实时地同步到TiDB里。

DM工具也支持增量同步,它通过解析MySQL的binlog来获取增量数据,然后实时地同步到TiDB里,延迟很低,一般在几秒甚至毫秒级。

增量同步要注意这几点:

  • 确保源MySQL开启了binlog,而且binlog的格式要是row格式,因为DM需要row格式的binlog才能正确地同步数据。
  • 监控同步延迟,增量同步要监控同步延迟,确保延迟在可接受的范围内。如果延迟越来越大要排查原因,是不是源库写入太快,或者目标库性能不够,或者网络有问题。
  • 处理DDL同步,增量同步包括DML和DDL。DDL的同步要注意,因为有些DDL TiDB不支持或者行为不一样,所以在迁移期间尽量不要做DDL,或者做之前先确认TiDB支持。
  • 校验数据,增量同步运行一段时间之后也要校验数据,看看源库和目标库的数据是不是一致,确保增量同步没有丢数据或者错数据。

我们的增量同步运行了几周的时间。在这段时间里我们一边同步数据,一边做应用的改造和测试,确保应用能在TiDB上正常运行。

第四步:应用改造和测试

在数据同步的同时我们也在做应用的改造和测试。

因为TiDB虽然兼容MySQL,但还是有一些不一样的地方,所以应用可能需要一些改造,比如:

  • 修改不兼容的SQL,前面兼容性评估发现的不兼容的SQL要改掉或者优化。
  • 调整数据库连接,把应用的数据库连接从MySQL改成TiDB。因为TiDB兼容MySQL协议,所以连接驱动不用改,只需要改连接地址就行。
  • 调整事务的使用,TiDB的事务和MySQL的事务有一些不一样,比如隔离级别,TiDB只支持快照隔离(SI),和MySQL的可重复读(RR)有点不一样,所以应用里事务的使用可能需要调整。
  • 调整自增ID的使用,如果应用依赖连续的自增ID要注意,TiDB的自增ID默认不是连续的,是趋势递增的。如果需要连续的要用auto_random或者其他方式。

改造完之后要做充分的测试,包括功能测试、性能测试、稳定性测试等,确保应用在TiDB上能正常运行,性能满足要求,没有bug。

我们在测试环境测了很久,把所有的业务场景都测了一遍,发现了一些问题都逐个解决了,确保没问题了才准备切流量。

第五步:灰度切流量

应用测试没问题之后就开始切流量,也就是把应用的流量从MySQL切到TiDB。

我们没有一下子全量切,而是灰度切。先切一小部分流量比如1%到TiDB,观察一段时间看看有没有问题,性能怎么样,延迟怎么样,错误率怎么样。如果没问题再逐步增加流量,比如5%、10%、30%、50%,最后100%。

灰度切流量要注意这几点:

  • 做好监控,切流量的过程中要密切监控TiDB的性能和应用的错误率、延迟等,一旦有问题能及时发现。
  • 做好回滚准备,切流量的过程中万一出问题了要能快速地切回MySQL,所以回滚方案要随时准备好。
  • 对比源库和目标库,切流量的过程中可以对比MySQL和TiDB的数据是不是一致,性能是不是符合预期,确保TiDB能扛住流量。
  • 和业务方沟通,切流量之前要和业务方沟通好,告诉他们什么时候切流量,可能会有什么影响,让他们有心理准备,也能帮忙观察业务是不是正常。

我们的灰度切流量花了一周多的时间,从1%到100%逐步切完,过程中没有出大的问题,很顺利。

第六步:全量切换和下线旧系统

全量切到TiDB之后观察了一段时间,比如一两周,确保没有问题,业务正常运行,性能满足要求,就可以把原来的MySQL下线了。

不过我们没有马上下线,而是把MySQL继续运行了一个月作为备份,万一TiDB出问题了还能切回去。一个月之后确认没问题了才把MySQL下线了。

下线旧系统要注意:

  • 做好数据备份,下线之前要把MySQL的数据做一个全量备份存起来,万一以后需要查旧数据还能查到。
  • 确认所有的应用都切到TiDB了,下线之前要确认所有的应用都已经切到TiDB了,没有应用还在连MySQL,不然下线之后那些应用就挂了。
  • 通知相关人员,下线之前要通知相关的开发、运维、DBA等,告诉他们MySQL要下线了,让他们有准备。

四、踩过的坑

迁移过程中我们踩了不少坑,这里分享几个比较典型的,希望大家能避免。

坑一:TiDB的自增ID不是连续的

最开始我们不知道TiDB的自增ID默认不是连续的,是趋势递增的。因为TiDB是分布式的,每个TiDB节点会缓存一段ID,所以不同节点生成的ID可能不是连续的,而且可能会有乱序。

我们的业务有一些地方依赖连续的自增ID,比如订单号用的是自增ID。结果迁移到TiDB之后订单号不是连续的,而且有时候会变小,导致业务出问题。

后来我们才知道TiDB的自增ID默认不是连续的,如果需要连续的或者严格递增的,要用auto_random或者自己生成ID,比如用雪花算法。

我们最后把订单号改成了雪花算法生成的ID,解决了这个问题。

所以迁移之前一定要看看业务有没有依赖连续的自增ID,如果有要提前处理。

坑二:大事务导致TiKV OOM

TiDB对大事务是有限制的,默认事务的大小不能超过100MB,或者涉及的key不能超过30万个。如果超过了事务会失败,而且大事务会导致TiKV内存占用很高甚至OOM挂掉。

我们最开始有一个批量更新的任务,一次更新几十万条数据,在MySQL里没问题,因为MySQL支持大事务。但在TiDB里这个事务太大了,导致TiKV内存飙升,最后OOM挂了,影响了整个集群。

后来我们把这个批量更新的任务改成分批,每批更新几千条,分很多批来做,而且每批之间休息一下,避免对集群造成太大的压力,这样就没问题了。

所以迁移之前要看看业务里有没有大事务,如果有要改成小事务分批处理,避免影响集群。

坑三:热点key导致性能很差

TiDB是分布式的,数据会自动分片分散到不同的TiKV节点上,这样能负载均衡。但是如果有热点key,也就是某个key或者某个范围的key访问特别频繁,就会导致这个分片所在的TiKV节点压力很大,而其他节点很闲,性能就会很差。

我们有一个表是用时间做的主键,而且数据都是新写入的,所以所有的写入都集中在最新的那个分片,也就是热点,导致那个TiKV节点CPU打满,而其他节点很闲,整体的写入性能很差。

后来我们把主键改成了雪花算法生成的ID,这样ID是散列的,数据会分散到不同的分片,不会有热点,性能就好了很多。

而且TiDB也提供了一些解决热点的方法,比如Shard Row ID打散自增ID的热点,还有负载均衡调度把热点的分片调度到其他节点等。但是最好的方法还是在设计表的时候就避免热点,比如用散列的主键,不要用时间或者自增ID做主键,尤其是写入很频繁的表。

所以迁移之前要看看表的主键设计有没有可能导致热点,如果有要提前优化。

坑四:统计信息不准导致执行计划差

TiDB和MySQL一样也是基于统计信息来生成执行计划的。如果统计信息不准,就会生成很差的执行计划,导致查询很慢。

我们最开始迁移完之后有一些查询性能很差,比MySQL差很多。排查了很久才发现是统计信息不准,因为迁移之后TiDB的统计信息没有自动收集,导致优化器选错了索引或者用了错误的join顺序。

后来我们手动执行了analyze table收集了统计信息,查询的性能马上就好了很多。

而且TiDB也支持自动收集统计信息,默认是开启的,但是可能不是很及时。所以对于数据变化很快的表,最好定期手动analyze,确保统计信息准确。

所以迁移完之后一定要记得收集统计信息,而且定期更新,确保统计信息准确,这样才能生成好的执行计划。

坑五:TiDB的事务隔离级别和MySQL不一样

TiDB的事务隔离级别和MySQL不一样。TiDB只支持快照隔离(Snapshot Isolation,SI),而MySQL的默认隔离级别是可重复读(Repeatable Read,RR)。这两个看起来差不多,但实际上有一些区别。

比如在MySQL的RR隔离级别下,一个事务里多次读同一行结果是一样的,而且不会读到其他事务新插入的数据(幻读在MySQL的RR下是通过间隙锁避免的)。

而TiDB的SI隔离级别,一个事务里多次读同一行结果也是一样的,但是TiDB没有间隙锁,所以可能会出现幻读。而且TiDB的写冲突处理和MySQL也不一样。

我们有一个业务场景是扣减库存,在MySQL里用的是SELECT ... FOR UPDATE加上事务来避免超卖。但是在TiDB里因为隔离级别和锁的行为不一样,导致偶尔会超卖。

后来我们把扣减库存的逻辑改成了乐观锁,或者用原子的UPDATE语句来扣减,比如UPDATE stock SET count = count - 1 WHERE id = ? AND count > 0,这样就避免了超卖,而且性能也更好。

所以迁移之前要看看业务里有没有依赖MySQL的特定隔离级别或者锁行为的地方,如果有要提前改造,确保在TiDB里能正常工作。

五、迁移后的效果

迁移完之后运行了一段时间,我们总结了一下效果:

1. 不用分库分表了,开发简单了很多

迁移到TiDB之后我们不用再分库分表了,应用层也不用引入ShardingSphere了,开发简单了很多。写SQL也不用考虑分片键,不用考虑跨库查询,复杂的SQL也能直接写,开发效率提高了很多。

2. 性能满足要求,甚至比原来更好

大部分的OLTP查询性能和MySQL差不多甚至更好,因为TiDB是分布式的,能利用多台机器的资源,而且优化器也比较智能。

OLAP查询性能提升很明显,原来在MySQL里要几十秒甚至几分钟的复杂查询,在TiDB里用TiFlash列存只要几秒甚至更短就能出结果,大大提高了数据分析的效率。

3. 扩容方便了很多

TiDB支持在线扩容和缩容,不用停机,只要加机器就能扩容,很方便。不用像分库分表那样重新分片迁移数据,大大降低了扩容的成本和风险。

4. 维护成本降低了

原来分库分表有很多个MySQL实例,维护很麻烦,备份、恢复、监控、优化都很费时间。现在TiDB是一个集群,有统一的监控和管理工具,维护简单了很多。而且TiDB支持在线DDL,加索引不用锁表,也不用用pt-online-schema-change这样的工具,很方便。

5. 高可用更好了

TiDB本身就是高可用的,数据有多个副本分布在不同的节点,某个节点挂了其他节点还能继续提供服务,而且Raft协议能保证数据的一致性不会丢数据。比原来的MySQL主从高可用更好,因为MySQL主从切换可能会丢数据,而且需要人工或者第三方工具来切换。

总的来说迁移到TiDB效果还是很好的,解决了我们原来的很多问题,而且性能和稳定性都满足要求,我们很庆幸做了这个决定。

六、给准备迁移TiDB的朋友的建议

最后给准备迁移TiDB的朋友一些建议:

1. 先做好调研和测试,不要盲目迁移

迁移之前一定要先做好调研,了解TiDB的原理、特性、限制,然后搭测试环境把自己的业务放进去测一测,看看能不能支持,性能怎么样,有没有不兼容的地方。不要盲目迁移,不然可能会踩很多坑甚至迁移失败。

2. 做好容量规划和机器选型

TiDB对机器的要求还是比较高的,尤其是TiKV要用SSD盘,而且CPU和内存也不能太低,不然性能会很差。所以一定要做好容量规划和机器选型,不要用太差的机器,不然体验会很差。

3. 做好兼容性评估和应用改造

TiDB虽然兼容MySQL,但还是有一些不兼容的地方,所以一定要做好兼容性评估,把不兼容的SQL和应用都改掉,不要等到迁移的时候才发现不兼容,那就麻烦了。

4. 选择合适的迁移工具和迁移方案

TiDB提供了很多迁移工具,要根据自己的需求选择合适的工具和方案。比如要在线迁移业务不中断就用DM,要快速导入大量数据就用TiDB Lightning。而且迁移方案要考虑回滚,确保出问题了能切回去。

5. 灰度切流量,不要一下子全量切

切流量的时候一定要灰度,先切一小部分观察没问题再逐步增加,不要一下子全量切。不然万一出问题了影响会很大,而且要随时准备好回滚。

6. 做好监控和运维

TiDB有很完善的监控系统,一定要搭好监控和告警,密切关注集群的状态、性能、错误等,出问题了能及时发现及时解决。而且要了解TiDB的运维,常见的问题和解决方法,这样出问题了能快速处理。

7. 不要把TiDB当成银弹

TiDB虽然很强大,但也不是银弹,不是所有场景都适合。比如很小的应用,数据量不大QPS也不高,用MySQL就够了,没必要用TiDB,因为TiDB的维护成本和机器成本都比单MySQL高。所以要根据自己的实际情况选择合适的数据库,不要盲目追新。

七、写在最后

我们的TiDB迁移项目从最开始的调研到最后的全量切换花了半年的时间,踩了不少坑也积累了很多经验。总的来说迁移是成功的,TiDB解决了我们原来的很多问题,也给我们的业务带来了很多好处。

TiDB作为国产的开源分布式数据库,确实做得很不错,兼容MySQL,不用分库分表,支持HTAP,能水平扩展,高可用,这些特性都很吸引人,而且社区很活跃,文档也比较全,遇到问题也能找到解决方案。

当然TiDB也不是完美的,也有一些限制和坑,比如大事务、热点、统计信息、隔离级别等,需要我们在使用的时候注意避开。但是只要我们了解了它的原理和特性,做好准备,就能很好地使用它,发挥它的优势。

希望这篇文章能给正在做或者准备做TiDB迁移的朋友一些参考,少踩坑少走弯路,顺利地完成迁移。

也欢迎大家在评论区分享自己使用TiDB的经验和问题,一起交流讨论,共同进步。

最后期待TiDB越来越好,也期待更多的国产基础软件能崛起,给我们带来更多的选择和惊喜。